From cb2855115f742e4d50d458083db6757b7a5b9741 Mon Sep 17 00:00:00 2001 From: andremussche Date: Mon, 11 Nov 2013 21:15:05 +0100 Subject: [PATCH] Websocket support for RemObjectsSDK --- ROdemoWS.zip | Bin 0 -> 32189 bytes RemObjectsSDK_WS.js | 218 ++++++++++++++++ uROHTTPWebsocketServer.pas | 314 ++++++++++++++++++++++ uROIdServerWebsocketHandling.pas | 81 ++++++ uROIndyHTTPWebsocketChannel.pas | 436 +++++++++++++++++++++++++++++++ uROSimpleEventRepository.pas | 137 ++++++++++ 6 files changed, 1186 insertions(+) create mode 100644 ROdemoWS.zip create mode 100644 RemObjectsSDK_WS.js create mode 100644 uROHTTPWebsocketServer.pas create mode 100644 uROIdServerWebsocketHandling.pas create mode 100644 uROIndyHTTPWebsocketChannel.pas create mode 100644 uROSimpleEventRepository.pas diff --git a/ROdemoWS.zip b/ROdemoWS.zip new file mode 100644 index 0000000000000000000000000000000000000000..9545d014bc6c89750220de5951a6ce8cbaf0e8eb GIT binary patch literal 32189 zcmZ^~V~{2Rv#r~ zxpHNtk}N0~8W0E&5D=~Jnb5z}|93$CyD~MiwRd-BRQi8|pn%{4kA=1$+_^+Rfq?p9 zfPgUmJIGwr#?s8rMZ(_6mci8Awphc~ew_*FTfp*%cxSbuhMZ9-y+t}+B{_y2YW3Zy zf`YSMczMXYCI4|}#y&8FY}T$q?dbaQ!Fh&XK=j&Qbc|jw0{DSeWZ`Am=2w@WnKtR` zaCKbDhGP*ssjLxKb}XAu(;;*Jz+9RTPMO-@L#s0I^9P*$H<9DI%Dr4G^6>GSD4>K*(PG>D0J>Kr!w5ZWcSoW2*5EycI_h23zN`M@Ok9vp=dxK#)3-d z(VX#h%(KJ@N9|iJ6_6a#o2wZZ}7Z!7XU{eMl_n5ens7(S2@g}@uONCa1JhMdLm#=~A+$w$X=v67T z-osf;y$PLw)!=WL$(>=MpOBOBPX2loblV|!9e8=~Ud6h}sfsKn$gzR8Uba5d#i`{MlG8B^fy_O8Of(^T^>S{LLXK2Zq z#V3h$mWaA>;uCi(%h0Eew$j0JOZ#K$T{D37MMYBj#0n5|qg#eR0*$bqR+(s|E>jAQ zz*cGYz+Vgphx3u(If$uV{erMmRaLXwo;YF9TzSbupX)zhLzu~6K9D38S^aHvy)MlD zri6z@2^FVGez#3V#=kg=i3~wcX>wAiN%yR=C!S_7oms3ZoK#VN+XSoRlv2K>D-Qi3 z&W(tTN8f?3-%R*riXGbvYSz&?czWW{^(dGiRR;V~8T2(WmaPg+E+~A4A$R)F;O<2!$S^X*kX8cFX z-Sfj?<$7VK<4hg@rxB#&kp1a|k<+Z2&z1Rr_`ZluD z`tV@L?pY_OR6Cw`7px_Wa*_X5M!{5O`1GE%q`@3BPl!;i3o?G_#4Ytz^3#WA zZ5`xoNePut_c~;5WbIY77kGnTy-SwjXdJ&XmHur=ZKapmAXr}-_bII0+>S`X_$V>- zd;!07IcVc{vGg&OCzSOyK+qJe`4_DK_d=NTZ?`uXusdC125vcr0uQEN?sAcNB6AWG zt-QQ`BG%M$=HA%E&QoWQsus8^gNCo9tNVdj+<29F;)C&){Y=e@Et-_oNOMRtw&Du~ zr$i*Hmb-!9dp}f>$;2(YHN!SdyHZ^~ry8+|8CDYKUk?lpKzh}c;i4MKUjkNy4+j)0 z)xf$gSV{HHOqJoN5*SX8wXEr+Xzc`A4J^oOHvOj(IWjrmyQx8cEt_|pMfObmu>-^( zx^v8#0>3qpZ0c_*c<1O;9ZVO(pdvTlsoA^8u))a+&Fj*R-_;KFSUM{&+aC4@9J5Jb zK+LL?5Hp^P`vGc$+-0FES}KFjHkPt)XD=OkB;90vaRy~UwR@41QuiWjiJAj7^IqTOggpRAYZ z3@#}eLX4YU7^_vonzNoyvhGEiuMJ_|JQ_a6uE$(3S0j-i7KCLbbfdc;y&jP4kdG8B zV{Dx|mhM48>_Ko;Q6pSfZ`c|CVhqDuQG;z+3smzJpH6`xeAguP-?3gO=^ z5ldoN3?ubeXxVd}4KEQ-ah2`&aTfcDNB+=^ukKUZwOR%{*lln--y0W4uo?t=q3>p? z0Am`Q#o!T1iE_00`oz!`MS;{68aUx{?bB@-E+EB(<&a&(QgVhOL79>#HhZc;BYPjW zu&}`6Tz|iDR;l@vjH^rnqmp-Qfon>qIykF*M3<`G*D?m8ux}TB@ZO(#NNm)Rq$q&@ zqF@NGw;wHp^anX`gOc(PlkH0mgzm{>r@p?Hc`_Q62;9ho6ZjjBK(|d+oh54dy3#;6 z9E9={ztyZwSu@i5rS=P(?-iv(c@knw@CV%ob{v!tIFCto-r6MAVc3=i2I@B^>&9=b zFT=q|ln{w>Y;)PyQ16^g1CbF!x94wEykrdS&BSfyiamkZKfl6TnH6#tEkDQf#Gp3^ ziFYSlb8xQXKpGO(P)H~O3oS&!Uk`DyYWgDj;|-`d1M3Il3uWSYApB)MFC9P#CqJ$> zq0T|YQs$=ji=%UbsLC-YPiiPU<`4E9yx*AK$cgaU@@LjW=0c`S(;lLEgF}raHuNf+ z!-k@+LVIj*)oXq;KBDa6;_>_v>tR^t(Df*#E=`}CQ3TCG^BY> zL?AtZbSFA!t*g<_kb+HaiR=wC+8~@u?H8hvOLYr?TugZjjmyG3xAJI==&OoW>n*Ek zB-xTSBk}f)WKsPaS>kG4*VzjO)AividV8ML7MM(gcYoFty&|Q#)vwB|WPEen4TZ;z zuS28?lA8!yr>dXrD)U(e95KLIN&l%di7*;O*&tOBMdQ%<^-T zC1T%Tj%}!DE@FPLy?-wjLR8Y30cmYy&S>e{R`p0@MMkiR_hWFd_akRvC!nP3-L?|r zlbIev=G>yeiY$hrvc=7V^$6>QqLgS&(7QR6ckigRdjk6jS0xH?!p}OL&iv+K2upzb z^@p_?`g(Q4X*Q5H>wP=!M5R6D22JO^abaPWQ)s+nXcUAvrnWgR&BM5Y308o$k8mMn zU%CRF%SO(5Dm(>90veEPUBKsyCayr<1TTwGOA|f^+Wqwd2_$jyk;t*8u+igtM%QnZ zQd9C~{}FJEed9}DoyY5ju7yrz!^!2`w93J(&J~xe!RDTCxx#T{=uf`mNtNv^ys%hn zK(p+lj*H-|!zuhFyqpAU!pr)Bb(qO^Ta@)gqy!n$+KKObwCyDwreBZ2uR7R3tLhe8 z?AY77{*unv(xQ0k(^S3h{2M3R{ipJQA?aGZ-4fTGun+PEcBf4gee)i{@7o7_EcCMV z+O|F=?m}(&_vjOMN3mf=hN-#YT#WF9Q?bB3K6AL!CYM|8HU7taT>T(W$jA z{7;Ur!~O5VK-J92&CKaPa{Nj|)*go$sYmnNe-*biL{BQ9q8tni%odf3=xIJ`EjvKR zgmeSx^#xyWRZ=TYnW1_S)|epY?fBsYnE3~~9-)M&mFPpHMgXkwug4DqRL?*n!gUaU zlx112YAzfH-ozzDPDtS?2bkyNJO_mWgJPcCS{_`|-VCJjk8OcZYNWg&S{nh79-aKt zth)R54cnZo%kp4~D+~o$2J?l*HYGFGNt)E(OISOFnZpqtNxCx`A{+?_Oo;ao>Qn}_ zr+{T+99f|4;Rm8C=4*v?P_8D{OX?2Lk`baXK8^rDYYNEfQNT*HEEfi)F#E!gz{l{Kv9j8XvzN0D z2TjWcqIqMz_n5+hcy6Vm;!?(`2lV{(RhF<2dlkY?@a~_gzW)1Y^@g+;L#`O}egaBo z2=QY)c`xom#Q`+-#FD2^tP>&^su$k@R3<0v+Tb9_2PqX~2VD)3@;^ne6Ock#pD^y7 z?nU}W5h3^D`eN;;{Cvnsf3>7OI4Y`_l_xYSl+brL>H_8V+qo+A9PAk0u3t04%$1!6 z8hmnn)Q(~MI;KuBBJX{ntN7r>O~!o4U^U=8e)C8ZfUVOl{JM^<;$Z!Fw5D6}xAYv8 z&Se=pyjsWmKy>E>k?bWcj@J!8`NOlFgc{u8li=5`@jgHBpd?Y<<6{3H^Mb%u(hYWc@! z{d6ajqbmaDKw@cj|A9Sqn|5xb#sqt)$t^ewQ{U!JP zZwHHhLl@)3iF;cZnXRoeBbNw}B;r-C7)r+Gi!OY>yxE*&0&S^Ye!RY2yPr6_Wl=Xk z3ttOwL$~9i-VAEH#eMmq*DkqsoxZ z5NL!F52B@3ifDUhHDg0oJT2LeNB{)abq@O^Fzs4}PZI8(GMgE|LW2C?%56OtiA;bG1} zQ3zf{Vj?pZY{}s&1*}|KlcLqjB6c4kwyL2o+5_11!;PLZTcdNB8n(nSLin4%-g@~q z59|QPEVOL4uUbg1 zmtnZ4n~-J;5wJEQyN=@J@*_{zDDxVofUC&$lGCpZ;Jq%M)#v=Z@@s)!-okfj;1 z{}Dp>Ail*adTu$wZ?eG;_M#JKOa=IN&2}W(w>C46*;%h;HSDDn1@5q9gpJ%HJgl%oU7p@L#p}?UH)RiPvlQ7VvSR4=!RgO+yXd9 z(8`yzFR!Dv9xe6MY&(Cn*e-yzbU>EPr!7GugeA{$08FKN1~2$^WN8p-A71l-jI_Zj zg}kwL1#d78g9+_h{AvGe;)^$FW6LvVult@6VdWiXfW0f3ps{>6cqPfDFW)8OWpj_PO)f3znVel|R3aBnH8 zcF(g7F=wrG1vQx<={k5{f6uV3^;P1&4&5UC8=K8Z9LFF+kX?EUAB+=xiL(s8qN$mh z^;#nREvVWRK01C+d#%rb+txX+5ABO>kSdT-O8B=h>8y6=<_q*cWdENQ*uRSU9}CRF z#n$Hk=Yu7MY>#1f9WyK99BtHg}pZQ3UiziQ9|1}?80pS|k>gZ~o{yf+kLhW4DVG$4=NEu5 z2paN6*09ug zS)4CRkPVk#fK=t}L-9}EEfLYO*{m1l%9q}&LWi}M&o2|{qx&33{x^D^^1lir{f*A` zQ0b}>1tCrd)aRrKu#Z`t22hTU-rmkBEQL4et`80cPnT)&YGQ2iA@ifT9g>oVI$T{N zSBXEEyN?$JcRT%DXmg~e67jz)e!s3>xkc2Pb?y8Qs8=;SJN6}PqhvaYFW`Sf!b_3ZDTorHPymm3g5P@(&o~g4eQp>a{9=rzK zbFtg4nmT~DZX+td5qJ;L$VpsfA#?y-(Wx;ls@< z4KKbv;p4c}Uapbb83nD$##z?%9^%F`y=G-O_+362U0t}zb9zarqj>QJkNu4;3e2Q2 zUxRnB|4DKvpcI8Ip)biC37UW9YZn>_h~R&uu7a7noTagok&~x^rJaj8gO&4?#?F5- zc#v8V*mb<6RCx%rx2YaJ(&B|pK3TdZM%!4kMRY~@=Vgmr0S89TTgcc>z5S$fX2;jv z4Q13`-(l$PSp#p7D-lD69G@-(T$x7?iW2qDiELWF2q|8PLpN9|v91wwS5N2)$N5u62& ztRt%lEg}){8^Sid6TfZaahKSM zt;TU}=TVD44zP+Xyu8O2XMU;>Ggl&S^J};sox;~Q-KTy(^gA!{^jQORd&-YRI^&Zv zu>j(vyn2OF22JY-B9p}c9yjLoZ`kG{-YcTRNA$?|+@h#%d^*j;s+)f=^4}E0DM6yD zCBN+Fb(}e9$94J>^)INlWLNJ>cd3Gx*w?W5NiyPuzew78xzzQnljak_xZxGEDv#>n z3A&XX<2Z^n$wjF2;tc*KuT+HsU}Ds(aN1aBOmGOG072E*>9qW_JyttO#I0D4trC@` z4R=f{nx2h9we@xf>5-`=`=`LVY5=)-?P$6pg)6A)`<2sBsymjhI;S7&fp-@>PF<{U( z+25!p=g=9DVG|R7SRWxo-gY9pOe<*~vh0@i2g^DqJBoAsZgt^#LvmtbP^zm~&Gt{_ z_jZMG&>Eq+337cAI|TaUMuq)>{+lu->vkbr{DBlwS|FghB_JTY|3R6GnXRI+m6?f) zv#OZvKgO1Pz1=rB?oW3F{b96TT;!J%=(r;eWE`Cn*xIM;5+At~GMAsG^2sJjNdI(@ z9Z9P0%=rC+Km;L@Qf_J7a&@wSj`K8P!s>$H*DFq4~b{i8#&;UF=icnpl1rZK~idum2*||$Ss|8)UQz~;-%r2UD)l=l_amE zc}>g}*XGRn{lIkWfuL2%&emASmsNNgs~E~5s(Iibd@CPo+JJf9RzS^)iWbV1MtU4C z05|#ez%&`8sxRilAhV@)s8Hy@A?$TMRW8*1DdN?b)drUdW)f-ry*{v@n$MbLF8H1)?DTfXRT6i?XRk6x(uG;Gwj zW$GA)bk^@_5?2{xA$Y_> z&D@?F7xXjj{TlK6$#&(@dO8>do=ho>VwzHl z>$+o+AM|jW0>tU`lsyRkfT}~#?SXE&U}TvS1qv(@WIB}=^!^AKI<-wW3c<5*Ks19>FZDKP8pqF4OA)VzgMEo|xm@pP%`TV&B{sDbPB!o!IY>Jh?VV9!`$)8r2GysDmWG#|>X6aX%Lf6G5h7$ZL;I&%xceBEb3nt>@0 z@wb)fl@|h?`LLghT3{V?2=@qID$NZV9yn#b2QY~L3W+sfxd;f|#}Fg>fjsTj+H!`aksYqe6-Co>Fp>s< zT1HdcQf7;)UcJaQF~i#PyOFNxaKxx|oZQooae+;`TVt+(C^$N+mkhtoU`FILS{p5w z<&!wGfc`V(}-RCY;#>Eo0vH4dW|gvO{@r#H!i8^4umqSbGW){>NzptH zTLJTleVU6A2^>oIFNnND&KC0; z*VyQ<*l|mC2UuYH{J1XpuI0>2D}jwG5?xP%Fu{P|F2!Oj6U_z_^g#|9=ygV8Kcs#&2qXGS(#g8ysbp#sfw zLB;eId{ewLiftVA@42}VpJ$3Ai42m@^NIn?Q0?BSf%3OcN^}lmAvV{wSqR{)xkkm5fsAI6FyHia>s*< z<=ik#2XsON1PUJ-xEGyD!9XY57seyX5)9rnOcDcP%P~1gy3XQz=y_UEgip28SqKQw!PJIiG@Jnh#J0X~etH_9bO;Uuqq}f-5~xh@ zmOj!KWxRfkyO6ewE|logvzjbU>Y!}U%XUhJH9tJz0Uy&k*=T{`MUYGb6l8Ueyqbbr zD$@q7B%o-x4R3q#w#AJjY=81Iw){@|vHWr1wDB{zQ4d!NvU{f};7v0h`$0LotpjYa z`NXGfUUAkMiav1iRN+)j;XuC-zqjBOJZi0To<_-ai(?+6(zTmv*wS=NdmPZK&3hQh z5Gme?9l?^JFIz^dR(DwVlR~G;QE`U{gZ6G{-d@FYH7=5=m+VoChq_ohN*`Fer$$$D z?o;uk4&RyQaUt{Ra=Ie==}O-_ky~9;7qUHlxAZhVZP}@Hn;_w)+n_UxxefNF%?Xak zneP4F`qka*W$PAbO?ud%p-dVKK0W^3SRJ-c}O2Vn|s_m%IY&5ggr_ zN+F$9WnAAj-)Y$5*m&Ad0v{QbAl;z8d-&u*l&u12o4Kh=>l%M@2mFDCvMlseaK^IV zkC-Ijk<3Q901I9f6cQ4hlDXr!P*^ZUKy7;zXMw&D2MLzlp-zFBTO}}zm>NW)yx2SZ zUM0sMeeO2?)VOrxaW{0gRaeiW%U2ra2u64V8hxtx46}V6YxTW9-}knC)`Fy5c-gq* z)$}5~eIe97n3p=gS2osRp`BFS$%gs)l|~^5iOQ%yy>VcQeNs~r_ID?LlO$~x%ojn; zPMehV4fQXrjN30G9~iq`)MKzHL5Nii#O^wha=R-TwzSH+$&mr!h6kZYF zXTAtyMfilp>*qF5DcVr{F~>NbF)AoR`~S zmJg_|>t)W)I6&XtG{Q%Tlj}@K@dYX_7j1Kou+D+{o`LPNObiFKUWL{bqt96BS9fGe zVb_NpZ_A+Du;H$iQ0l$L7-Y5xIVqZnJhux$RD)tpo$n|gh!oRNlNhjC8V9d^vf~2e zB*n-S3nzFP46!;w#^BSg8p!#R&-2%d%}y;YAG(KXeCVuTxcs8Ty;DF+2;`#~FW@w^Z95|8qh5;MXt4 z8QyD~+*bu9wFsIFYj@Q95S4nmk+|9Ho-Tk#7NMF*p$|*8xsd|EXaIOF93Cbqfs^|> zH=cBUg?JZ>i`&m3Xk$gIFe*$nflIJZZMlCwH*Uh;lXo! zw?o&zDuBtTsG232p1mwgBh?N!)Sq<9qTb>ZET&2tf5T?_D!nS~=;b^2d2+)$FMbR5 zTlE&=dH;e(Cysl~C@%t~Nw`WJ3zQqLRo9t_@=7a6u_nxo^yzpL z!nLO(nl#T?d>NUPaFZv)N44asCF7|EIqW?fb_`_s$_0^6VucPs!%7wX|xl7v$0l?qD!4UodCuI{EGIKlYc#OsK~a;>=41u z4Kr2FQYlidAgL&%lOF-i!=9QrT>86sso8v#ybYvbF%;B_eX~Dl-SEHOq#=NQQKy?; z#}46;6aBt}**&eS&`|MCZ{bX1X5D1)+Zx3Jb!DyMkK0-y(h|Ni(jL1ADgpU?Lz=T9 z|0Jx3^G58j4|nB7W5eKrrzVbL%nv0)c07Q$WD;XQLgO;y|UlY6sq6 zCiE|I&Z)ztWKLll^r0pcBXAfhyE#W2t5D%4|M{#uKIPWRrbLo`seCfAw5Fh(<6Fo6 zlIVP?W`$o^bPrX;a-;XA32<6W>2-J7T;G>XCGYUPoz`Wr%QX1(=Ww_xt+5$A@XFQT zR>od*M=NyEmVoFX*pFA)?s`Y39u37;o~{kNrL~RF&=%r{7_aR5KhAA zc1)$mYt?SmiG1jd;^1qHE)FL(%>ZZiP!q!DcDowOrt50?zIj_ayA;3(#rx^$Q@tp) zqQm!AQ7P&t4jcY-K&B|-m-IP7aMqNBzZbPf{=q9 zy^^TM<+}|VtoLpA6y-F-gcKTyDGG^Hoiup+ugE`>C=opdG$7V|kLqF>ha7t#N4oDB zDC8^_n6lxjtpt+H@bmN*+E%u9aWK47g%fEjRtGa8W|ZEr<@np`s9PPIRzhw)BUYrD zSO%s#Q*15hy2_G=a-k_XlgzIdFK%yxRv9YMLR^%BDwsS!YYu-ZqmMm@(r(jGw0lTr z7<(JVFY~ZA!Hm!H?uxcLT3sm6?yX6*0h$Ks(D3N~5eM^atA{H|YCkt&fyxgzG|^$- z&bTwFH1<-{n+?bAv3S9ZtBT<)zFy?+EYbDN3Z3D?tNKgTiZ8I=H@!~JyJ?)PVM@Ta zqWi)_HWGFM9)ID_vSyFtTNu1oE)@QrgDYd9M}jVavuhD{VEX**ji$`Syh1AS3evLa z4mvNsLshyd6kCq$b$&SF5WJV-C&>${HB9!<(CKj#C6tk?VDk*JTaSlu!B_`(IUBS!fUXj_4`tyU8>Zh+cVWSQY)^ zyv>(xWgh@~DR#SjBb&wE9VGB$JdNkh&dYfE@ zvXslsuz4mUDVA;43?=}yQ_G8ONMr-D#u}{nHKu1x zj35wm4uY^X!`T?juE{oY!r|X9DAR?BBdZNGHWHx=W7MV-6A{k4-L4)>#V%XPFMy1+CEtJsU=pV~efYQ|zSMb$)IuAoWhW219Lg1By&D$pm- z#l=h^{TVSq;ruO@8Bp9U#~43{W~n;UM89a*yZKxPfjF_4*Tc7b#4FmyX&dS{4$d-= zQ$tb6Rk7FU&MRf8biP6*_z{7#-l%v{6{ukAr*3Fn<>%8WvBSRjG=mQuvht^M4M6m- z{q>$^AO(0pn;y_09RN|_(>oUCu~S`zMtms78kM`??qNEbOI{fxYF;Z}abNS-(sK)w zc+PzBw_b0;>~2SNf}bu^uTISjCg3F=SzrK+ZK5*`qf4q3UYT@=ON)A zlpF|D zUec#p6sxnN1TPASiqK2+%_3){cK9W^VdNrBcN;|&3)T8B%VXi8@czCFJ>j1LArM~P zfD?MPRi^7h&rIcK4kcugYNS5|rK&Zq=v%{-xM$K0Q?TI;;R~7!sxLw+kmZPOv{p&i zMMy?|i6-1;(Q#xF*9(gUXm*M^?8-;Pl=LC3YQVZI2R){h!mL-a4IV3-WhBy#FjhQj z1w%~MmGL*vUW{Z;`6I4J<)?J*KCxlmRxmhh;Q<)Dyuaqo(zP4+kZUC{#Cgxge=DIyXxd0{*BoqIypKE@v{vlz z4@zvMXT9qHzc?ZqF6IB+GyC_PIyio?LpBqtuLm%-=KO^{+@FI2pnYK1!s&FP3V?s* zUHM+o7n1RER26h%#$et}+>} zca^~X2K*CTTje{@nce6k1K9o9iIpn)Z z%pqP#l`nJ`VG!Priv6;Q082mn&xW$K^Xg@ZgEQ}lYWeF}*BDq=9VG~vJQ~S2lXq%E z8LsymMB!Ic{7+94;uk-Cp@0pdZGc2&>u;P@LN6QRZiKFtzWdd^sZFv@cQ`odeLR(# z^6E~rSsPEOnoW(={mOPg6-JPzvXd6+3~*1suB$xRPY0Xv6*T_|mC zt)6de$38}kqsvplwN0Tn_)f5R2k0Pms-{5}gAmat~`J4cK zb-^_0N~6XPufY3qY7O}Is~#~is`xt?dTWn|YxSr4+}4^wed*89KlcJ^kx=K%vkQ82 zM4mo^K>6Yxnz`Gy)V#=H=X<_0iV|z>^l|U&N_?+mKi*YQa{bf=t~uOw$43VXL@)V2 zt>i90Xs3;r>0@g4_wMYHweun>>!4rT)mm#kQ8e^y&4jd**Y@cersvQ`EDfaYx8RER zgw&-=S}d&df?QxT)528@SuVgLkK@rHl^9+{V0K5PQ8=>i64ZPTYa`pEG1v#Qrm*(d zbXVr04+y796^p6hmf@Lc#RemFdiTc@g7;HyZWUhAqQ@Q7!%hSFRc_?ha1cj+8DwMc zDn-4mln9w@&AaHq1GpRg@C}IIQYv1tC$+i~@e~dZ?i%6j4sUE98b<3>$-uYJCbhBD2m6 zST)8hBf!*dlUcfGHhcVA(5Mkpl!RM0@MrWCBCLR91@K%$ah44TXnIIob4q-_eQ#~> z8oaoHl=t3Lt7CJJo+DuPSR>rHogMT0Q>{gw7IY(CR#0`!2Redd)6RMO7V{1=AQ@VK zz(l?c`xdU!hw121y9K|$Y*N>BrqG)zWg#iIN>4%EO7Nu}JA_Up_q@HGPsDRNl~``d zys7vKN|pxOBSS-B{pFQ=uy*-6M)nB2E3a#O7BI^pLZamMTQjT92S{JH46))cfTdgM zSm$vxTO?NQmTok3Dfq4&-|#orV8AW&w)p*WT1d6Fn3iUzgSM8^>fMJCz_BD~Z8~2Pf&%qM+yB z9BvdHsbP62DI838unq{nW+WNO&ffx)KZ#X`;1??7DHFZ<%7Db07(6Z*o5?4i*i#bV zx2J$l*dq2^_S7gST<(%v?8^GiitbT{mu11{shz?NmD=m9-LH_(jX?)I2~6{%=J7*G z0-%NP<`R`|qLK<@qUHnJ$WyT{y67EoBb;HgyJjg6Jx>Tz-Brh2kAeq@RGQjRN=CK7 zUmNsVk<&VTgVh(0L3X-8#&7b&JLaob)EO>l3r)e5RB+Us`+C9DfIj!h&Dy$_18u3O zExekD#a?;doQ@DT05O{hcT%brYjO?v|48h+E?6k(UDpZ_^S);134mGpGW@`I3H3t42^R6q> zDk{#GsJZvCf>DtAx~nnt7$@S~srFQX8t)AieP4VdMXag^n>1DW!`0k?p#+I{4U~Lw zb042Z4?R_2OUyc2s|rY@NM%uVZtb`2fXaUrs?tUe?%(J4%KRP z)Yw1zlqkL9&{y@#SZD+L765@<9}CRt?rT^rTfb)Q8zG=`LUi>rr(Jf`hJ2E z({$1F5j_vg2gR-fbhyG|A&O|y@Ee)yfOe$^$Jh%l!Pr?}I=Ez{Kc4oW0*W^9-g)V%Peapas0V;XczZ##)RO^^nBS%FfEhxkkc){{ zj)+-(2pwr*XHg@wj+azdp|C$^^--IF2`8hW4zgh{?8X56rGb2?pGoM8^-;AfkK6X_ z428mL&e#Q(QTCk)%;`RzYCibskIGTxDjoQ-dk?bdd9=G&spZRI; z*wQR;f~9;Hn}si^8^|n-muNMbV{0Trb?@JK#Ne!lbQ?yR)w4UaS#MKaZaDOTD%nJf ziMKr&l;e~R2-b9*OixxMfKu>`Ohc;jby|;ZgK!~Y@juRvjZ{eA=@Q+9K z7b^~IkMeMNqqLERMSUD7!D|p|cx3C6@tHync{xmd0+%Ch3fYvMn5p! zm^MMpRb*$LE6gdiFt52T+J>{jIDO^asBZD!1@}M%Fo4hzt3}WMm&FyoK>&3C6g(z7Tny-<= zw(qmzEHCpPSWO8%fSOqnh;zCnAE0$0%KLqb`w%r*N%}Qhd9pak_iaYTZR2cB3NQrA z=Ew6-5r=1U5mFdzoO+kj!NU><2Kc8hBV8Q=2!hIc%C-b*Pa(Sc! zysIOlg^jDl0tQBQh1J`K*%U=j4BPWS=)^;s(*uf*Q-URWgX2MKwCM8VW({;2>X%=_ zsSyJ|L$oy<@^~DNRQ#xow+;!9NAJOBt*kjZo~A2N2IsrN_<kwe+8bdPpr+WmgIC9zv-jM=Id$!fJDvrZT(()M#q<`@`;i>!4LiXoMeDsshj~ zSY}!{JivCMU`-(s*bCZ0Td55tV(R#9zZ3rr^l%}2$53XM z?CyI*$3`4WMPt?D-ZY4b1Gn-Epx^7XrOkbUWp+z+ORf!bYX*QN3JtHl^M0G$L#;e> z?A`AgD2H!Ag+hcSRgcQcm8zoEJGuhF_JNF>nCyA)Lh#kJ8rMJ2V7wyl|fG{L-DWqoUD3e?k8GN zWh2Yo_on?CRI3!nszUUJ#j!}jr{F8I8E51a*QwbN>3{Mx%bZrOwR&Zu>sgi(Ey7HIL&__y9b(|A zsI1C5(=uAL;QuM?9e^wQwzcorwmY_+j%}x7b!?lRblkCR+qP}nww*8k@7#O4&#Aum zO=Z`rRPFWKbFG~<$Cz{ANsefO<4B+!WedT4eVryzdH%c1V zZHdbxyCSp0Hd*8JTCtY)x(aMw0y_RsEI-Swl}k7Bd=-JibGtQGT0Eg8yCz~rxZWzW6WC57;9Fr0_BlW#pN`cBe`{9Y>i)U^e*g#vAw zKnQ?vSy>gi0a*|CDD6&%D?hX_Q)i4O3}Z7~bU{yH)B

0u9Bl#fmixWR{8`Z)1^Si=iwP_XVyrs6h(pp+R+ALFuc1fUBCM>%e7SP@!GO0 zIRHJy4t2hE8TOtOy9o;9GOG${B~1jTScCMySAGogagyRL^Voy}5~+syCg7w2ZN z3RzWx?1OPAqYA(_W2lzSMAr-6g-oUg57MLFf-g6Rmc$ult#LMzBVL4#IYLo_mNl&J zJLoyQr8d@M02+|?oD!KC-=;wF5n3+v(jbfCIvyUTzc^fEw&QEG15)6W8{~wF1?1Hj zFXCp|94H=#?4Dlip1G5lzcGEkf;r$Jt1hm01{zO6VU_ZBd>`*ye_=Zukx{BQ_GkMAY+ZZa|>^ncuypz*BZyNIvj^5xByL z&7riJ!*;7Tuma*ciR-*YL2Yo(cMOp zmY@0SU;eM;S{)U|&-~glOfCD*{910NCwOdbW`ok`c49VX{ka(X*j-vnePe3l>&RU| zLV{oh&_e+FkdtHZ2OS4M1QcFAaia&~PtibcOmRYXjP#hH1aA-v!ZY4C>~Vx-tyHe_KG{~1M>V%Hpt9UVGS>xqvR64l@( z0T(oNL+0)i6^+L-1rg**DjCC<2W-SjLIH)Wu0yQSyNwiCes&0?Z*_b=AX2va)tPN&aN%jK$Lfn98&cMGe_eK!{>Au z?-&ne5`^Om3gNJ-G;h2;2fRyYLX){_g(4(KzuOO~D__5Z(;qft3J9nFqCOf<;~cleKCTa_Lx(hQ}VNtPuSY$#8eUL@+4* zW79JGYal2V5hphu|2?wrW=Bp==Z=M2MmT zG^LNh{;Z!8J)fIvJVKg-UOIWPS1c6UR`phE*b0{6G$s+qxt!(R8L2pXzC=u0^3}&+ zUc!1~g{5(>s<5V$4`C*)_iBti$uQ zMOw2hh;uyl`pc)WTJ6mC-o~;}O-piq(^`EY1}}Ut?3x=mj(L7<4FI;AXIe z^S<=tHagpR(MsJjj>{I=x6jZ-_o1*rdwJk3%S|$1@Jey26HZ-fODL%|OK2XwA z)Cv1Fyi^bbJR^uYd^2~_bEjZ#BHzO?LyA?1^K>t)F;FOkv3F6`WJLnrZ^|!7ct|+) zOvhQ1OYrE2bA<9LMq=_|R45I6i1kyhnQdwb+Xxqy_nj6N9Nc^v<5e@gu{A9D6nA)9 zTv;?}D5pIs>~UVTLQSn#&8EYbAkp z>#dTl)^404TuNE!aU7xNr#818HP@*IH`Db!U!I<D&dF~Zl^Pf; zo!8XtxSYDo$m&6S{K1xHJD!hhkoIc3NZff;ZI7@iDFfT)X-8_`4yC&bYlTb&1 z|MKd>2~sK|cs->rT4@y9@6n7`(hQTCTOYg@igVZlUe-L%*>|B$&5RFp&A%I_HAA>c z{glsT#t^8BkVYzKv$Owmx0oerJ-nPE`>gjujJ=zNP`Zc>@(BOb-R5fDKJt2ZtL>oh zoXzY3tN&`3>dDHr#i;Z?&t3ki5L97rm$s^(xT;yj4!JL@3qn*|KD&jS*^W)nxo(>z z%^2xMJ!hsUWeyqv;i1HGn2iMsH8NfXx}YGBxNgRlm`0k&KTT1BfPH`%!E-JFkOtK& zT{AU7q;SYG-0Opm{$n{tM{D;!Iexy?>f)4sYOS8u&WJ>AuO%7hOZoacR+2|v-qJL7 z-A#~mRRX$E<;RUs0)%33Lac5?A@8wWI)-s<$j0W>4lzt1KF2<#Dw+a(l}i03^hlu$ zPK#Xl##z;Zt|_15Vx^;QzNHUL5@h?Vq^}cd-JoIWQu>&%*9?rhX?5(}8k(Kl5D*X+ z>)FC_$_bCT`_3*CaNFmSN~SK~oQ| zt+vm{gY;^s|5-g3@L4_gM+3Cqg>$ra)&>@N$}3hotO%ZTlis>_p`I38EteFI_Mn|Vcqi8p{t>!no*eumlR3O{= zATQ@ywPWY5DUZ5wV~1K!biA8dX*yVhYe2t*gmp;pJbFL#0lha?sSI_jniPPJYv#!*|<}4h+>B3!o=PcX+C#s4onVX1Vf`J%mN|0Ig>F~-2pxVYG z+yAJ$Yh8JyL?O7h@qAiI7(GsUXPo6$@&Gg{sRp(5uB24I9D9L6(JExSx^GpjXwW^P z+Cb@|<697E=T~*Te9CD9?Xo^!xB{>^fgAk=wHh}E6g&4GtmeJwE4=jRT9|Uyw@|T= z5M%3DwHooj`FL1MRAKym*)kjk|G*&J4k)i1g7GK&EQ+a^Nj}1GoKh?<#-kGRm0fNP zt}FMh@y5ApoiMr4$_vJle8;Uc(6wU~4&LHg8cafl;56NjPmkRNw<|S_9+Qm?6T&^b zZNqc1&3t2Yc#J+1j?+&lKg(gBPh=2W&LBRIfjU%oc<>o{!YKnf_ z_vIH2lb8Q2p4|pVnIRW~*oHBS6K%uy6BL%;6>knTVjWZLgRi8Ml%sxXgD-Jom@Q$> z7TJ^cAG1OT@&#)_DQ5Rn!Yo#JCnX5!e3?9gIx40q$O;y4Cj{ng;u~06LiV;Gj0+(O zCmC%F$`IJ1kDVtE5_-kQvQ&`j(?}*yC%qvdao2+S_0YG|gkd~Ysk4s=eq}xyL^DL(Z@s;V@Ag6GcYy9aPYH+3au3%*m36eArL#n~@@*}@!=u(= z24zL?wD4?wbc$Wzd6aXLy8vx+*_rEK*e?OKw{PwO^`m~>o8P3!; ztO?l=ydq*hzT;5>P6xE)j^%;w0SXk#*=GUCs2n*67&!wxprzcniJQcJwCP`1qlu}L zIEN*B+MQmpF>QFH-$l#Wz%IDh_mPDZ-8G_?`mbu5_6%-DvWqctBJ| z5KGVq!Y)6kl~|9Lz=A34c^}y3-qOGNisUz28Htxsp5tP14^1i%Qwyxnr5*sa%7M%T zLvFJX2L2*C(i3o(frxS+;`{|7C`hbg6GJWGf}ifFh+r?BXP>cVrz_GoZnhOaHf!DC zc6^t^=g`3e1y%4#qD_KgRA+{-JONsZjGk~5sB_vl26Rb9N!$P#l+FnQWNFUE3C5xq zUL4$<&qt@Vj<+{g5oI~&;xD-`_3p3trS5JzPdH18{#v&J{*%a5PoM)mHu8>n_;0mX zx|7C*$p~Y$GfC?91NS=eDVg$NVzMov0m&4WFtb+BO{?ZMuD`4buOvb+vrIMRo_MCK z^6bV?!1!COv7i>{rJJWic82#8_Uc9oBZYy#?&6Pu`x{GIOE?oWELe@aax{vdMu~Ec zzG}Q9uPZ)Uh*%Z%Uo}En2xmb z>rxS|IzBB`yf+kXRPqo$5)v#ry3B0gzg?tHm3T--90HW5Gm?(%ZQjP~-)g74-Jpu4 zi*I+tW=m0cz$yO;pZxNKhSAmx4FtC0&qku5;PTe=jf?*{hty4v?v{ml+f_f@FmEb{xiLGE>HfKkzG-USwlMye4P- z-lLY5?VxlPPi<-=Is0QFdvN}A@!|?d@`(@&!K_a_a0buC^2~aBLD~irp0i7(!E)M4 zb-%dK(N{>_p)PR5Oe<4`S-)g)IT_YvhQ01ph@G(&GlIqrpR|lHhs44wg0Ty;Z&myK z`qMAb%)wz(ax-e0LWa)RIb~2ekuclMQ}NTHq{(!Ajp6}OjsjwuOD09?k!sBi^rPS0 z_%=wjNYD}W=SGE{&?N5XhC7|&&#r&XBf*j)xSVV&82?b(9|6;Zdlm%B&z|8p9hP(1 z6c?kA!aG~8*Vu2N9{q}#HZHty<@bvnS;OJyf}z(Be-<8=4Sf&N$r7`Mb##?z!X}a1 z$&w^l)rv71SNjjHr7yu_~pcpM07qYzF)OISC0OZ6ydL z;`l=W1>8jup4JxHZrbN7S&>8b9c07f_m0=ucOtSMGPrIJ+*fsK=qY#i>fSZ=X-cFU z4GE>_#5Wb(uOI6@&>OjQk8TIxmsTHnl9hMjKA8TUl>~?Noc%Bae_#(_I+VVRaR%7Fc#!I!T#Z)`wBFy<4*58m7-C}_hEboWDDe{P5W*( zHiYJmsI9}2IJ*N7TLaY;!pc$LD7So*B??&n@zG5r-o zO|i0$k9BMB*)D)_$m43#?e06`3}P3}%_pEHWI^FXU4pG)n@y_V0_g9A_bkl>c;E!d zig2%W#`J=#NI`LB)I_8GLCEm=dnt93i-sWbl^?U&CGk4&>cc@2x6yn$>i9HHB4mKv&6nQfuC_r?ac>HF^2N&aB_{3# zO&T#D$L{WoYnQ7BI385QfGwi@(tK^@#4wB#=$9~97bj=)@+DPk6qXBSD|ua@%*l(6&Clyl_GoUujo+SK!E+H|;&=S$842S=yELS=HF3m0&G zoLIOrPt}h@c~9big7Ua95bQk9rzi~R8FasMu(|0^AFRGpBrRL@6tTfC~A%Kjn z7IFwoL#R5xd;^sFd0^(}ZTY1DLtDnrd}iUz)@bvodC49&ELhzfT_$!U7?6oT$GHB4 zpYD17j^8tH{Bsg zf`VcgF^A#GNnY2TQ~By@L)8_75W%UdB#s_CU_N)G#_^1Rw60L_V@mbBD^8fHO!oac zYXATR=j_^mquNFT1S3b z%YGu`o`?&+5e~UC#cjRK7xH9bjVXhie0cOlJ$@$Ajn;T!_e8NC8LS29{$LZx&27|F zXD(>tdB6w+@&J$tMI=mFk+h$vR(*ZRld_q2$Aa%;)og&`TXj#U>C_CqG&T__S07wU zdVyy3LH!zi4H|fYDBAH_A1X3J&VFoC!2o{FBGe(g z-`m(cAdy>c)SD7^Tb&s%7FLzc^ZBVh$Z7elB1DAD@brDY8)7FO(!fV2C67MS$01X} z|IF*cy~R&Cxf0SzXdRZ1Y0hvfI5Lp#$eXu=?HfLc1SD7o>ffvsh)s7{5HI}F{Pie= zK`2|9*=+`YLRa<$9C1JxnSlz9gx$rM^F0wm?QwALi<%5Ypfbz7+0sP*$7}Y}HT)OyIWTgG=ys$R56{@#77~JCX1kdbTY{ zj~QGEw&%N{#?U&&W7?T=#M)VUo+YhhU74XhCa&#AQdIXmK7 zojX}#dKgSJb6ixE6$w_P#7Pb3CsW?XPj~Dt3@TNoV1%uO96>!9{kPwBaGGK70z}^O z^An%DBH}z_^y~G|2548KLO$o-JtvN2DM(@d%i4Sk!ANY=wwz#|DZvBU#D_xn{sfcr znAWKm85;GL8+xR@v9Le8jQnG-Uu6mM%RhG`K#~7qLDXp;o4c_%8}FgM#uI;+H02`6^#MTg^Vgf(oC=oBZ_TnAS@+w_ZF72 zyN!k)2Wib5FUD8sU%zb}OV_oX>#Oe@sRn%n~l zx;)>!YhT^G(=wBv;ZK=kp4oHpya*MB#^C*6tz1a*v7wp;qXCT2S0(6OR*0%Ev@7Y( zV9PJ-YF5_-_TRu^Pr~Q?DMv=J@RLROxhLmQyQyRMN^8<&MXq@AS6=qKFrm=80h(n4 zuuTW_&XhBIM3JQlxVF+tG-=Aj`@0J+IXbLM8()p!jH+_-9uEn4>(_|SHb1EA8{gDO z?3_Do&|L#Kt9QR7K=-aBMPV0hmijSUhz;1dK}9tRtLkGwUsKdw$M42(4I+HjS7~e? zV)~tCb)-%}O+kJnV>J;`KPhkyBQ1Evl<8q!Llrs>e~s}Ecd?bxbY=&WZo+Enx4o-b z+mBzD4>}pe-;J_DQ=5K}*$lW;B~uE@!6(P24^VhQEJMYf9y9K%{!C1jW1Z`-s$WKk z4y|(wuT?=arcmh$C!YY%j=7TE)XEx3X&|ny$2{zY+`-5-Ws^v&$J-6G#xR1bs9To% zhD+)o#5?2hK64mGfKk>JZgy7SD>c2CLoMT~Z^Vrwck{C=W+PeD*f-_`;8Jv zgHpY(a-JLR9J|2^c3kn8!8$#aqwWeDHjT0qnc38#UWuC;q~`tCR0ieKg37T)3!YM7 z7)@ncVMX4VMqSAp&XHtsZ)U9 zw{dAgL3y=TNd2XNEUEDJOF`imiUq1bhy($WD}rsNT%@<0nU$A#v80}OwzZdpsSKqM zRPHDd3lnz^Na5gOCqOU!gcQZl4_n0>1(&8j`W#h#j*Xf7Sa$Ps#Q^*{u>WIhpEFRI z>WTFVE5gTTdwO6MhoClBiJ6G2uhe%$Ec?t!Wz>BE1d(|$W2&Uw(IF?GFB{GtV)>{` z;`JDwa=TaQyz970OvQu7s$u-AGZG|0s2pA$--@A0bI7%-bg3hs0nacf&>N zMEUB+)U7B@3(ra-0#j#H01kw;>U1c-#O+TDj(zVv zB|bg19P;u^?4m`h@+h@%b>AzO3p^HV!2X|< zwDThO1@x`WK9IhqaK&;{l?7EWU%5smM-`g*r-w*@I8HUy)a{*6mQ7{}xAskr@T1Fu z;fd$pE&Mo3i(g<_UsB@181Mw6p*=B*Exx+wp)mJ%A|VtI*Ycay-;L*GJ~WyHHd2XP zsD;0U=-B9QRei=pqw70vMC9`NOtr>(6RV%f(kxZt&aWk%0`TUVnF+z;X+yX=@ElfL zgc6mAoB4!<6#3F~B-yO$Aw>hBllfZugTObw833B~Bb(~;i6t{33o(wP!%}U-xDs6# z6lNnoSEZU-%~QTiK!^G{_$+4~@JGNv)t}*%h>^G5FM{g#CEQW9RUjNd1*A&bb}&+G zgVn6WZ7TX$(PPs$%4nN3F5;sf7W-l8EB$&q7)bO@U&mf+pW4}-rMMlWW-`A&|6#;4 zWMbi*q#DE}0vq(x-ayMr39r&X%ibX6+eEB*UJpaGPMr7SszeRtv(agg{4{=5=txsB zK+CDBW04PdK7Qxcj<;vQ*2PVc1eq@5MoRbSCMWn4ph9(lD>W zKBK^|Rk`>})cAzi=A4PXY=7~usYml&k9t1^e&w%7$D3T@D8gdnZxRNG12cGy?T1z* z2F!ru$C@Vb^nvqcfx~5l8o3DqhJ#mYfwbCho~;eT)2?2Nr@pwUnUx`@7gNIuATl)V z3Vp-##wo(o4sm_&c_gF6GuLbnWbn)ieIII&*VnrA><9+fSOF&xpDh z8mLc8G3#7b(wCIn2^IgU474)X?6n*rKQRauEWJJ?SB}Mw>X8>u4#^5_L zvf8%J5f<^Vn8?LsJY-wS6&Ik0a^zSRfZ^45c6?w)V6nKRK-1x;AmU3l1mXw>X#o8> zC&tmP)vC!rTzB4Y$b*>>5&4M$Prf@u?_LNg|F zqWCCR`yA{c{jU6Epu-D6rw6G8<`q?Kl@`bW4y^zgu7SD}k{h>Dw`Crj`pm%$b?dNq z82exNUqjJ)w_@uDwa%ubruI2$ZqnLsQ(N6dkPd~{mtBSS(`^w*Tb@Paw5{uvvcmAlRN9isj z1>@oH*}Va)8ZSab%Xod3e{rh%3m(oF-uAns{rw_NpA+ciJ8Iu;qYpo|ddRAn!2C8tCk`aDWtEZ?n9 zFRMhxGhHIg!5WOk8GUChsupa3huu&=oE0Y708$RU#|YRKlH=V?s)m}k9h^_tI_Z0c zJqL3jL**aj^Mhq6%@ampLao{8KvA>tevx7l$BXUCHP##{dArE}l&^)npbYbM9V(+x zcxPDoNR$s@h?tg5_2J8qWpntZ04B(#D%jf3J{G5%fYCtKi4k~6(pE9sOpzzmQGDTk zu|#*1Y$#*4m(-Iz-r)KHc%{x{5%(G`i}{Ut_;BM~Iy?GJ)9tDNiXGQd-7g1?LYwh+ zre~rrr#q*_bHtkZ_7_kF`hq4kO&>R+Wd8*|dozKhh3U-KYnL@B+89@xl5Im91J~2$ z7z`?>HI-Bjv?@}AbS9^DyJsNk#R|2o`ii6ab>$Y(@IGKuKRs&So}b=-_$;H}_W3dj zXuZci{gpgG0Dw=`n*R>SD9EZ9>M2_5n;SaV|4TE9*1*QjQ*H##mmZ-t<`SMe@eyJR zoE;O1hzus$g|<>pe<`{iC-(7TdM*fl+tS#`*omo3xo#hJWEvR7{abohvMLlG_EY+W zBUtDK!@QwsX)=gEEY2$V8b)$>grO+CkHvdHPkai;iRd;OR}NAs?F!chEE#NCs4Na5 zG0E!`MU+4^rFa7l736cB&^g7{+C8RZ1-Bu%1pGw>v>Bm;h{1) zjAlEKgl(v)1{Cv6G)>T79;HOOJFTx*2DorIx~R|VxW9e3$ zk*fCh`(SO>rlRwYljME=!xMahd~=r|y6jbLW)VnlL?*1U3X$yWm(P!Qlaw$aNaDGE z?E9hn%81&E${Zm{C2BUiW*R=->vJ#X0nT;YyN3+#Hd9&EN6?n+dz8mm=VK~@?1%iJ zHB>kRHlXe5$Ux}tjo)?M9;xnoAefjoZxSEVO<7U~9A6GH)`>%Y5Y4MlhvxXjQgnm_ z8-I8$e_@`G9d{Ew7_T@GB`b)xcUZS(jcESab=5MzWLRRD_BzGtC{?^iMCgFkKiYSH z_Q;ejEBJs2y?z6k-+f6q&&m>oG6Q|&OKpC+gUBziT*S7Hm)qMTQZQpOYY9HFW4Iy} z47Gpj27kE${WIt>ajBB0etF;CZp$Hqen+>{lwf0?>w*fg$6Q2!lfH1`0a+Hfrxq*_%%=g+t<|3U+G^AGZ97=ICvQyD# zbpTKha30Zc6^An%1ai=NQ5!#lN=Rg+@0poLuf+0XEs3Aa#C%b@q= zP%1$^YbHc!OwzJei-LpNjS`W&Lo>DXRz*kf! zJdYHTle9|ZuEi;-W})V3wq*=fwSMa+x4{u0Ae5CE>uGtbp$by#d9nx%yvS}1;q(`s z-OwFZ(2xGDjT~J`F-qAdw|k^}#nKs74ryfgQzd;obulY=U?#yaP-d*M$=7soV@DMy z04ul@uTSX`Nl2xrnvh1-7(6 z;)IfcpOhiwEy|WAZ3x4+Z9M{K<8h%gI*_PNX=91vF2uoCI3V>?H&Jt_qbZ~Lo3|

P+baYoA18>OoLG>>RT(Zlt57$LZO>W3f2U-SS0pM?43s~Yf;ZL@ z_^#*r2n7}zr7gPyS4EcgJgq3crpF(O{ILrc}TSkrmcXhn^q0s)R59C znJ%vg-q$0Mi-=PHcpX>}iHQhxDxnI3U(jytq0dW5N8n!kv-Ag7u&|q%O267j0XKVG z5jQir-wQC2KJ}^oiyh?!*!2UOSbI0Hr%oah))};TI*D%K92ZYHOmvw!PzB2mwE|^| z($vDKOz|59?C7W^NJ60nyBbWp7Ju+dQX6__%wU6wGB7zDb_kDD=CEz<&ti839wm(1 z9y6ZsOQfeZdUd91&ESx0XBby@zSF1<7>u8KK<9%sQxdfNDGz!otEWV-J@P+$!6x78 zfX@`FTyNt->1! z&M)Htj*GQK1hM5?1#AO*WD$`F3anCrv9 zU_emg%a1$z?us^$;Z(O=R{tK9vb=5S| zqHs2mUN$yw2z#fU+puD{8Ml&1=?;{o3gE5#l(+>p=X~!w<(|p+P{`T_Z`p|8f%CeE#)`HQ>Vcq7q-fI7Rd2p?DlRb5EhmGFGyBtpuW7fp) z*G#>SS6{F*;c~=y0Vuhp_a5ZB7U8nO7@o_>Euvze<>Sp0b#|9oW5t6P|&vWTqeR_)>GX(rjy|HOIwq zTBJth@2x-vbv$Kn(HI%(ndJ4m3|1OL*P>JT?44k`)^*y<&WOm-0a6Q?p1$n))5-zF zTx}oL)Y87j%S)8YON^%$Zy8Q#zE}W#;Fa(vt@jXN?=pXy%&|s9`J7eh*jVwaVx)lwo*xDWWhKDzeT2mMHqS~|> z)MYPgN5V}ux`>)IVJAbeL!f}pxChmGaW7MHh^t~JurO4E!m3erOCxR86(`f-?tJG< z^svkp%g!wNgE`LepcJMEy_e@)>4DA8@=j)CBEfyRwJwLJLj3nX*tFiBND+CtR$;HsR{szmeDh`K zNQ8TfuK~Uz#7>byY@-ptcOYTF1M6Jr;k@HU$Pfi$-9pR?BJa zgyHebO0=|)YYA8?usGe4qISx?UU%Mj4!X%v>#}{Ip4QiC2rpl*VkV$+OE?tM>v5bP zwMvppA@Q{P_Up(}@p}e5Vwp+IQHQ;e5$3WNwNWc`&Rwm-J$%|&`B~x)=}_7oK(6Iy z5HJrBdA{BL`L8evZIJSd&Yw0GMaWUxJRy(66^vJOGF8R0258HR(raVb|>lcmSahK1m*({t%zw^=d?18@~?5NABw~HJxB#ylHIwkV127 zP`j)vpjKtLeW%~fX#Ebc)*Y*y4Hv$vA6#gF|?;mw>X`3+A|Ln%Lb zX#2vzJqw&I7AQCBWt-~u1d@pf_G{aq$Db~0x-cP-i8<#Yl1U(j>k-_p?xR~@?y*2v z{YWh|4dmpb>b{Y!3Xb#OhiI`9_3VFU*#QEf0Q~PF!JjL;&ma1K z9sYZf_xq&3iw6Je-T0pelF!$F=?wpegz%q?ze%9}W~6*t{J$_hcbxxZ{7o(GH=`E% zH{;*6)Ba@qP5kLMg984qjDH))-(_b0#rWqY0)MU&|La=*OLXSHZ$AXU-(+Wge>%;7 zocV_c&7YU>Hvy2}kVDje|M)-BC;v+hk-VTi zg#HiGzbd@`g#9f``!|eK`hQ@*ukQcP+5VI9x0KS~j5fKyFn%YP{wL@^ld67$K$QOm z`tKC0|K$8BLw%hU>rELEHKv|pp3H#fU;y0|_=C82de(ay9zd6CbQ7U%- Z57a;70tNf$gn#B?zfVt^$mO5E{y%WhJIDY4 literal 0 HcmV?d00001 diff --git a/RemObjectsSDK_WS.js b/RemObjectsSDK_WS.js new file mode 100644 index 0000000..8734666 --- /dev/null +++ b/RemObjectsSDK_WS.js @@ -0,0 +1,218 @@ +//*************************************************************// +//some extra stuff for Websockets PoC +//by: André Mussche, andre.mussche@gmail.com + +function WebSocketsWrapper(url, aWebSocketClientChannel) { + this.updating = false; + this.urlCall = url; + this.WebSocketClientChannel = aWebSocketClientChannel; +}; + +WebSocketsWrapper.prototype.post = function post(passData, isBinary, onSuccessFunction, onErrorFunction) +{ + //if (this.updating) { + // return false; + //}; + this.WS = this.WS || null; + if (this.WS) { + if (this.updating) { + return false; + } + if (this.WS.readyState == 2 || //CLOSING + this.WS.readyState == 3) //CLOSED + this.WS = null; + }; + + var parser = new BinaryParser(); + + //check websocket support + if ("WebSocket" in window) + { + if (isBinary == true) + { + //add messagenr to end + var sMsgNr = 'WSNR' + parser.encodeInt(12345, 32, false); + passData = passData + sMsgNr; + + var len = passData.length; + var data = new Uint8Array(len); + for (var i=0; i C_ROWSNR) then Exit; + strmRequest.Read(iMsgNr, SizeOf(iMsgNr)); + strmRequest.Position := 0; + //trunc extra data + strmRequest.Size := strmRequest.Size - Length(C_ROWSNR) - SizeOf(iMsgNr); + transport := AThread.Data as TROTransportContext; + //no RO transport object already made? + if transport = nil then + begin + //create IROTransport object + transport := TROTransportContext.Create(Self, AThread as TIdServerWSContext); + (transport as IROTransport)._AddRef; + //attach RO transport to indy context + AThread.Data := transport; + //todo: enveloppes + //read client GUID the first time (needed to be able to send RO events) + msg := Self.Dispatchers.FindDispatcher(transport, strmRequest); + if msg = nil then + raise EROException.Create('No suiteable message dispatcher found!'); + imsg := (msg.MessageIntf as IROMessageCloneable).Clone; + imsg.InitializeRead(transport); + imsg.ReadFromStream(strmRequest); + transport.ClientId := imsg.ClientID; + imsg := nil; + Assert(not IsEqualGUID(transport.ClientID, EmptyGUID)); + end; + //EXECUTE FUNCTION + Self.DispatchMessage(transport, strmRequest, strmResponse); + //write number at end + strmResponse.Position := strmResponse.Size; + strmResponse.Write(C_ROWSNR, Length(C_ROWSNR)); + strmResponse.Write(iMsgNr, SizeOf(iMsgNr)); + strmResponse.Position := 0; +end; + +{ TROTransport } + +constructor TROTransportContext.Create(aROServer: TROIndyHTTPServer; + aIdContext: TIdServerWSContext); +begin + FROServer := aROServer; + FIdContext := aIdContext; +end; + +procedure TROTransportContext.EventsRegistered(aSender: TObject; aClient: TGUID); +begin + // +end; + +procedure TROTransportContext.DispatchEvent(anEventDataItem: TROEventData; + aSessionReference: TGUID; aSender: TObject); +var + i: Integer; + LContext: TIdContext; + transport: TROTransportContext; + l: TList; + ws: TIdIOHandlerWebsocket; + cWSNR: array[0..High(C_ROWSNR)] of AnsiChar; +begin + l := FROServer.IndyServer.Contexts.LockList; + try + if l.Count <= 0 then Exit; + + anEventDataItem.Data.Position := anEventDataItem.Data.Size - Length(C_ROWSNR) - SizeOf(FEventCount); + anEventDataItem.Data.Read(cWSNR[0], Length(cWSNR)); + //event number not written already? + if cWSNR <> C_ROWSNR then + begin + //new event nr + FEventCount := -1 * InterlockedIncrement(FGlobalEventCount); //negative = event, positive is normal RO message + //overflow? then start again from 0 + if FEventCount > 0 then + begin + InterlockedExchange(FGlobalEventCount, 0); + FEventCount := -1 * InterlockedIncrement(FGlobalEventCount); //negative = event, positive is normal RO message + end; + Assert(FEventCount < 0); + //write nr at end of message + anEventDataItem.Data.Position := anEventDataItem.Data.Size; + anEventDataItem.Data.Write(C_ROWSNR, Length(C_ROWSNR)); + anEventDataItem.Data.Write(FEventCount, SizeOf(FEventCount)); + anEventDataItem.Data.Position := 0; + end; + + //search specific client + for i := 0 to l.Count - 1 do + begin + LContext := TIdContext(l.Items[i]); + transport := LContext.Data as TROTransportContext; + if transport = nil then Continue; + if not IsEqualGUID(transport.ClientId, aSessionReference) then Continue; + + //direct write event data + ws := (LContext.Connection.IOHandler as TIdIOHandlerWebsocket); + if not ws.IsWebsocket then Exit; + ws.Lock; + try + try ws.Write(anEventDataItem.Data, wdtBinary) except {continue with other connections} end; + finally + ws.Unlock; + end; + end; + finally + anEventDataItem.RemoveRef; + FROServer.IndyServer.Contexts.UnlockList; + end; +end; + +function TROTransportContext.GetClientAddress: string; +begin + Result := FIdContext.Binding.PeerIP; +end; + +function TROTransportContext.GetTransportObject: TObject; +begin + Result := FROServer; +end; + +{ TROHTTPMessageDispatchers_WebSocket } + +function TROHTTPMessageDispatchers_WebSocket.GetDispatcherClass: TROMessageDispatcherClass; +begin + result := TROHTTPDispatcher_Websocket; +end; + +{ TROHTTPDispatcher_Websocket } + +function TROHTTPDispatcher_Websocket.CanHandleMessage( + const aTransport: IROTransport; aRequeststream: TStream): boolean; +var + tcp: IROTCPTransport; + buf: array [0..5] of AnsiChar; +begin + if aRequeststream = nil then result := FALSE else // for preventing warning in FPC + result := FALSE; + + if not Enabled or + not Supports(aTransport, IROTCPTransport, tcp) + then + Exit; + if (tcp as TROTransportContext).FIdContext.IOHandler.IsWebsocket then + begin + //we can handle all kind of messages, independent on the path, so check which kind of message we have + Result := Self.Message.IsValidMessage((aRequeststream as TMemoryStream).Memory, aRequeststream.Size); + + //goes wrong with enveloppes! + //TROMessage.Envelopes_ProcessIncoming + if not Result and + (aRequeststream.Size > 6) then + begin + aRequeststream.Read(buf,6); + Result := (buf[0] = EnvelopeSignature[0]) and + (buf[1] = EnvelopeSignature[1]) and + (buf[2] = EnvelopeSignature[2]) and + (buf[3] = EnvelopeSignature[3]) and + (buf[4] = EnvelopeSignature[4]); + aRequeststream.Position := 0; + end; + end + else + Result := inherited CanHandleMessage(aTransport, aRequeststream); +end; + +end. diff --git a/uROIdServerWebsocketHandling.pas b/uROIdServerWebsocketHandling.pas new file mode 100644 index 0000000..ec28ed1 --- /dev/null +++ b/uROIdServerWebsocketHandling.pas @@ -0,0 +1,81 @@ +unit uROIdServerWebsocketHandling; + +interface + +uses + IdServerWebsocketHandling, IdServerWebsocketContext, + IdContext, + Classes, IdIOHandlerWebsocket; + +type + TOnRemObjectsRequest = procedure(const AThread: TIdContext; const strmRequest: TMemoryStream; const strmResponse: TMemoryStream) of object; + + TROIdServerWSContext = class(TIdServerWSContext) + private + FOnRemObjectsRequest: TOnRemObjectsRequest; + public + property OnRemObjectsRequest: TOnRemObjectsRequest read FOnRemObjectsRequest write FOnRemObjectsRequest; + end; + + TROIdServerWebsocketHandling = class(TIdServerWebsocketHandling) + protected + class procedure DoWSExecute(AThread: TIdContext; aSocketIOHandler: TIdServerSocketIOHandling_Ext); override; + class procedure HandleWSMessage(AContext: TIdServerWSContext; aType: TWSDataType; aRequestStrm, aResponseStrm: TMemoryStream; + aSocketIOHandler: TIdServerSocketIOHandling_Ext);override; + end; + +const + C_ROWSNR: array[0..5] of AnsiChar = 'ROWSNR'; + +implementation + +uses + uROHTTPWebsocketServer, uROClientIntf; + +{ TROIdServerWebsocketHandling } + +class procedure TROIdServerWebsocketHandling.DoWSExecute(AThread: TIdContext; + aSocketIOHandler: TIdServerSocketIOHandling_Ext); +var + transport: TROTransportContext; +begin + try + inherited DoWSExecute(AThread, aSocketIOHandler); + finally + transport := AThread.Data as TROTransportContext; + //detach RO transport + if transport <> nil then + (transport as IROTransport)._Release; + end; +end; + +class procedure TROIdServerWebsocketHandling.HandleWSMessage( + AContext: TIdServerWSContext; aType: TWSDataType; aRequestStrm, aResponseStrm: TMemoryStream; + aSocketIOHandler: TIdServerSocketIOHandling_Ext); +var + cWSNR: array[0..High(C_ROWSNR)] of AnsiChar; + rocontext: TROIdServerWSContext; +begin + if aRequestStrm.Size > Length(C_ROWSNR) + SizeOf(Integer) then + begin + aRequestStrm.Position := aRequestStrm.Size - Length(C_ROWSNR) - SizeOf(Integer); + aRequestStrm.Read(cWSNR[0], Length(cWSNR)); + end + else + cWSNR := ''; + + if cWSNR = C_ROWSNR then + begin + rocontext := AContext as TROIdServerWSContext; + if Assigned(rocontext.OnRemObjectsRequest) then + rocontext.OnRemObjectsRequest(AContext, aRequestStrm, aResponseStrm); + end +// else if SameText(context.path, '/RemObjects') then +// begin +// ProcessRemObjectsRequest(AThread, strmRequest, strmResponse, transport); +// end + else + inherited HandleWSMessage(AContext, aType, aRequestStrm, aResponseStrm, aSocketIOHandler); +end; + +end. diff --git a/uROIndyHTTPWebsocketChannel.pas b/uROIndyHTTPWebsocketChannel.pas new file mode 100644 index 0000000..59e6ac8 --- /dev/null +++ b/uROIndyHTTPWebsocketChannel.pas @@ -0,0 +1,436 @@ +unit uROIndyHTTPWebsocketChannel; + +interface + +uses + Classes, SyncObjs, + uROIndyHTTPChannel, uROClientIntf, + IdHTTPWebsocketClient, IdHTTP, IdWinsock2; + +const + C_RO_WS_NR: array[0..5] of AnsiChar = 'ROWSNR'; + +type + TROIndyHTTPWebsocketChannel = class; + + //TROIndyHTTPSocketIOClient = class(TIdHTTPSocketIOClient_old) + TROIndyHTTPSocketIOClient = class(TIdHTTPWebsocketClient) + protected + FParent: TROIndyHTTPWebsocketChannel; + public + procedure AsyncDispatchEvent(const aEvent: TStream); overload; override; + procedure AsyncDispatchEvent(const aEvent: string); overload; override; + end; + + TROIndyHTTPWebsocketChannel = class(TROIndyHTTPChannel, + IROActiveEventChannel) + private + function GetHost: string; + function GetPort: integer; + procedure SetHost(const Value: string); + procedure SetPort(const Value: integer); + function GetIndyClient: TIdHTTPWebsocketClient; + procedure SetWSResourceName(const Value: string); + function GetWSResourceName: string; + protected + FTriedUpgrade: Boolean; + FEventReceivers: TInterfaceList; + FMessageNr: Integer; + procedure IntDispatchEvent(aEvent: TStream); + procedure AsyncDispatchEvent(aEvent: TStream); + procedure SocketConnected(Sender: TObject); + procedure ResetChannel; + + function TryUpgradeToWebsocket: Boolean; + protected + procedure IntDispatch(aRequest, aResponse: TStream); override; + function CreateIndyClient: TIdHTTP; override; + protected + {IROActiveEventChannel} + procedure RegisterEventReceiver (aReceiver: IROEventReceiver); + procedure UnregisterEventReceiver(aReceiver: IROEventReceiver); + public + procedure AfterConstruction;override; + destructor Destroy; override; + published + property IndyClient: TIdHTTPWebsocketClient read GetIndyClient; + property Port: integer read GetPort write SetPort; + property Host: string read GetHost write SetHost; + property WSResourceName: string read GetWSResourceName write SetWSResourceName; + end; + + procedure Register; + +implementation + +uses + SysUtils, Windows, + IdStack, IdStackConsts, IdGlobal, IdStackBSDBase, + uRORes, uROIndySupport, mcFinalizationHelper, IdIOHandlerWebsocket, StrUtils; + +procedure Register; +begin + RegisterComponents('RBK', [TROIndyHTTPWebsocketChannel]); +end; + +type + TAnonymousThread = class(TThread) + protected + FThreadProc: TThreadProcedure; + procedure Execute; override; + public + constructor Create(AThreadProc: TThreadProcedure); + end; + +procedure CreateAnonymousThread(AThreadProc: TThreadProcedure); +begin + TAnonymousThread.Create(AThreadProc); +end; + +{ TROIndyHTTPChannel_Websocket } + +procedure TROIndyHTTPWebsocketChannel.AfterConstruction; +begin + inherited; + FEventReceivers := TInterfaceList.Create; + //not needed, is ignored at server now, but who knows later? :) e.g. support multiple sub protocols + WSResourceName := 'RemObjects'; +end; + +destructor TROIndyHTTPWebsocketChannel.Destroy; +begin + if TIdWebsocketMultiReadThread.Instance <> nil then + TIdWebsocketMultiReadThread.Instance.RemoveClient(Self.IndyClient); + + FEventReceivers.Free; + inherited; +end; + +function TROIndyHTTPWebsocketChannel.GetIndyClient: TIdHTTPWebsocketClient; +begin + Result := inherited IndyClient as TIdHTTPWebsocketClient; +end; + +procedure TROIndyHTTPWebsocketChannel.SetHost(const Value: string); +begin + IndyClient.Host := Value; + TargetURL := Format('ws://%s:%d/%s', [Host, Port, WSResourceName]); +end; + +procedure TROIndyHTTPWebsocketChannel.SetPort(const Value: integer); +begin + IndyClient.Port := Value; + TargetURL := Format('ws://%s:%d/%s', [Host, Port, WSResourceName]); +end; + +procedure TROIndyHTTPWebsocketChannel.SetWSResourceName(const Value: string); +begin + IndyClient.WSResourceName := Value; + TargetURL := Format('ws://%s:%d/%s', [Host, Port, WSResourceName]); +end; + +function TROIndyHTTPWebsocketChannel.GetHost: string; +begin + Result := IndyClient.Host; +end; + +function TROIndyHTTPWebsocketChannel.GetPort: integer; +begin + Result := IndyClient.Port; +end; + +function TROIndyHTTPWebsocketChannel.GetWSResourceName: string; +begin + Result := IndyClient.WSResourceName; +end; + +procedure TROIndyHTTPWebsocketChannel.AsyncDispatchEvent(aEvent: TStream); +var + strmevent: TMemoryStream; +begin + strmevent := TMemoryStream.Create; + strmevent.CopyFrom(aEvent, aEvent.Size); + + //events during dispatch? channel is busy so offload event dispatching to different thread! + CreateAnonymousThread( + procedure + begin + IntDispatchEvent(strmevent); + strmevent.Free; + end); +end; + +function TROIndyHTTPWebsocketChannel.CreateIndyClient: TIdHTTP; +var + wsclient: TROIndyHTTPSocketIOClient; +begin + //Result := inherited CreateIndyClient; + wsclient := TROIndyHTTPSocketIOClient.Create(Self); +// wsclient := TIdHTTPWebsocketClient.Create(Self); + wsclient.FParent := Self; + wsclient.Port := 80; + wsclient.Host := '127.0.0.1'; + wsclient.Request.UserAgent := uRORes.str_ProductName; + wsclient.OnConnected := SocketConnected; + //TargetURL := ''; + + Result := wsclient; +end; + +procedure TROIndyHTTPWebsocketChannel.SocketConnected(Sender: TObject); +begin + if DisableNagle then + uROIndySupport.Indy_DisableNagle(IndyClient); +end; + +function TROIndyHTTPWebsocketChannel.TryUpgradeToWebsocket: Boolean; +begin + try + Result := (IndyClient as TIdHTTPWebsocketClient).TryUpgradeToWebsocket; + if Result then + begin + Self.IndyClient.IOHandler.InputBuffer.Clear; + //background wait for data in single thread + TIdWebsocketMultiReadThread.Instance.AddClient(Self.IndyClient); + end; + except + ResetChannel; + raise; + end; +end; + +procedure TROIndyHTTPWebsocketChannel.IntDispatch(aRequest, aResponse: TStream); +var + cWSNR: array[0..High(C_RO_WS_NR)] of AnsiChar; + iMsgNr, iMsgNr2: Integer; + ws: TIdIOHandlerWebsocket; + wscode: TWSDataCode; + swstext: utf8string; +begin + //http server supports websockets? + if not FTriedUpgrade then + begin + if not IndyClient.IOHandler.IsWebsocket then //not already upgraded? + TryUpgradeToWebsocket; + FTriedUpgrade := True; //one shot + end; + + ws := IndyClient.IOHandler as TIdIOHandlerWebsocket; + if not ws.IsWebsocket then + //normal http dispatch + inherited IntDispatch(aRequest, aResponse) + else + //websocket dispatch + begin + ws.Lock; + try + //write messagenr at end + aRequest.Position := aRequest.Size; + Inc(FMessageNr); + iMsgNr := FMessageNr; + aRequest.Write(C_RO_WS_NR, Length(C_RO_WS_NR)); + aRequest.Write(iMsgNr, SizeOf(iMsgNr)); + aRequest.Position := 0; + + //write + IndyClient.IOHandler.Write(aRequest); + + iMsgNr2 := 0; + while iMsgNr2 <= 0 do + begin + aResponse.Size := 0; //clear + //first is the data type TWSDataType(text or bin), but is ignore/not needed + wscode := TWSDataCode(IndyClient.IOHandler.ReadLongWord); + //next the size + data = stream + IndyClient.IOHandler.ReadStream(aResponse); + //ignore ping/pong messages + if wscode in [wdcPing, wdcPong] then Continue; + if aResponse.Size >= Length(C_RO_WS_NR) + SizeOf(iMsgNr) then + begin + //get event or message nr + aResponse.Position := aResponse.Size - Length(C_RO_WS_NR) - SizeOf(iMsgNr2); + aResponse.Read(cWSNR[0], Length(cWSNR)); + end; + + if (cWSNR = C_RO_WS_NR) then + begin + aResponse.Read(iMsgNr2, SizeOf(iMsgNr2)); + aResponse.Size := aResponse.Size - Length(C_RO_WS_NR) - SizeOf(iMsgNr2); //trunc + aResponse.Position := 0; + + //event? + if iMsgNr2 < 0 then + begin + //events during dispatch? channel is busy so offload event dispatching to different thread! + AsyncDispatchEvent(aResponse); + aResponse.Size := 0; + { + ws.Unlock; + try + IntDispatchEvent(aResponse); + aResponse.Size := 0; + finally + ws.Lock; + end; + } + end; + end + else + begin + aResponse.Position := 0; + if wscode = wdcBinary then + begin + Self.IndyClient.AsyncDispatchEvent(aResponse); + end + else if wscode = wdcText then + begin + SetLength(swstext, aResponse.Size); + aResponse.Read(swstext[1], aResponse.Size); + if swstext <> '' then + begin + Self.IndyClient.AsyncDispatchEvent(string(swstext)); + end; + end; + end; + end; + except + ws.Unlock; //always unlock + ResetChannel; + Raise; + end; + ws.Unlock; //normal unlock (no extra try finally needed) + + if iMsgNr2 <> iMsgNr then + Assert(iMsgNr2 = iMsgNr, 'Message number mismatch between send and received!'); + end; +end; + +procedure TROIndyHTTPWebsocketChannel.IntDispatchEvent(aEvent: TStream); +var + i: Integer; + eventrecv: IROEventReceiver; +begin + for i := 0 to FEventReceivers.Count - 1 do + begin + aEvent.Position := 0; + eventrecv := FEventReceivers.Items[i] as IROEventReceiver; + try + eventrecv.Dispatch(aEvent, TThread.CurrentThread); + except + //ignore errors within events, so normal communication is preserved + end; + end; +end; + +procedure TROIndyHTTPWebsocketChannel.RegisterEventReceiver( + aReceiver: IROEventReceiver); +begin + FEventReceivers.Add(aReceiver); +end; + +procedure TROIndyHTTPWebsocketChannel.ResetChannel; +//var +// ws: TIdIOHandlerWebsocket; +begin + FTriedUpgrade := False; //reset + TIdWebsocketMultiReadThread.Instance.RemoveClient(Self.IndyClient); + + if IndyClient.IOHandler <> nil then + begin + IndyClient.IOHandler.InputBuffer.Clear; + //close/disconnect internal socket + //ws := IndyClient.IOHandler as TIdIOHandlerWebsocket; + //ws.Close; done in disconnect below + end; + IndyClient.Disconnect(False); +end; + +procedure TROIndyHTTPWebsocketChannel.UnregisterEventReceiver( + aReceiver: IROEventReceiver); +begin + FEventReceivers.Remove(aReceiver); +end; + +{ TMultiChannelReadThread } + +(* +procedure TROIndyWSMultiChannelReadThread_old.ReadFromAllChannels; + if strmEvent = nil then + strmEvent := TMemoryStream.Create; + strmEvent.Clear; + + //first is the data type TWSDataType(text or bin), but is ignore/not needed + wscode := TWSDataCode(chn.IndyClient.IOHandler.ReadLongWord); + //next the size + data = stream + chn.IndyClient.IOHandler.ReadStream(strmEvent); + + //ignore ping/pong messages + if wscode in [wdcPing, wdcPong] then Continue; + if strmEvent.Size < Length(C_ROWSNR) + SizeOf(iEventNr) then Continue; + + //get event nr + strmEvent.Position := strmEvent.Size - Length(C_ROWSNR) - SizeOf(iEventNr); + strmEvent.Read(cWSNR[0], Length(cWSNR)); + Assert(cWSNR = C_ROWSNR); + strmEvent.Read(iEventNr, SizeOf(iEventNr)); + Assert(iEventNr < 0); + //trunc + strmEvent.Size := strmEvent.Size - Length(C_ROWSNR) - SizeOf(iEventNr); + + //fire event + //chn.IntDispatchEvent(strmEvent); + //offload event dispatching to different thread! otherwise deadlocks possible? (do to synchronize) + strmEvent.Position := 0; + chn.AsyncDispatchEvent(strmEvent); +*) + +{ TAnonymousThread } + +constructor TAnonymousThread.Create(AThreadProc: TThreadProcedure); +begin + FThreadProc := AThreadProc; + FreeOnTerminate := True; + inherited Create(False {direct start}); +end; + +procedure TAnonymousThread.Execute; +begin + if Assigned(FThreadProc) then + FThreadProc(); +end; + +{ TROIndyHTTPSocketIOClient } + +procedure TROIndyHTTPSocketIOClient.AsyncDispatchEvent(const aEvent: TStream); +var + iEventNr: Integer; + cWSNR: array[0..High(C_RO_WS_NR)] of AnsiChar; +begin + if aEvent.Size > Length(C_RO_WS_NR) + SizeOf(iEventNr) then + begin + //get event nr + aEvent.Position := aEvent.Size - Length(C_RO_WS_NR) - SizeOf(iEventNr); + aEvent.Read(cWSNR[0], Length(cWSNR)); + //has eventnr? + if cWSNR = C_RO_WS_NR then + begin + aEvent.Read(iEventNr, SizeOf(iEventNr)); + Assert(iEventNr < 0, 'must be negative number for RO events'); + //trunc + aEvent.Size := aEvent.Size - Length(C_RO_WS_NR) - SizeOf(iEventNr); + + aEvent.Position := 0; + FParent.AsyncDispatchEvent(aEvent); + Exit; + end; + end; + + inherited AsyncDispatchEvent(aEvent); +end; + +procedure TROIndyHTTPSocketIOClient.AsyncDispatchEvent(const aEvent: string); +begin + inherited AsyncDispatchEvent(aEvent); +end; + +end. diff --git a/uROSimpleEventRepository.pas b/uROSimpleEventRepository.pas new file mode 100644 index 0000000..9b8e3b4 --- /dev/null +++ b/uROSimpleEventRepository.pas @@ -0,0 +1,137 @@ +unit uROSimpleEventRepository; + +interface + +uses + uROEventRepository, uROClient, uROTypes, uROClientIntf, + uROHTTPWebsocketServer, uROSessions, Classes, SyncObjs; + +type + TROSimpleWebsocketEventRepository = class(TInterfacedObject, + IROEventRepository) + private + FMessage: TROMessage; + FROServer: TROIndyHTTPWebsocketServer; + FEventCount: Integer; + protected + {IROEventRepository} + procedure AddSession(aSessionID : TGUID); overload; + procedure AddSession(aSessionID : TGUID; aEventSinkId: AnsiString); overload; + procedure RemoveSession(aSessionID : TGUID); overload; + procedure RemoveSession(aSessionID : TGUID; aEventSinkId: AnsiString); overload; + + procedure StoreEventData(SourceSessionID : TGUID; Data : Binary; + const ExcludeSender: Boolean; + const ExcludeSessionList: Boolean; + const SessionList: String); overload; + procedure StoreEventData(SourceSessionID : TGUID; Data : Binary; + const ExcludeSender: Boolean; + const ExcludeSessionList: Boolean; + const SessionList: String; + const EventSinkId: AnsiString); overload; + function GetEventData(SessionID : TGUID; var TargetStream : Binary) : integer; + public + function GetEventWriter(const IID: TGUID): IROEventWriter; + + property Message : TROMessage read FMessage write FMessage; + property ROServer: TROIndyHTTPWebsocketServer read FROServer write FROServer; + end; + +implementation + +uses + IdContext, IdIOHandlerWebsocket, Windows; + +{ TSimpleEventRepository } + +procedure TROSimpleWebsocketEventRepository.AddSession(aSessionID: TGUID); +begin + //no session +end; + +procedure TROSimpleWebsocketEventRepository.AddSession(aSessionID: TGUID; + aEventSinkId: AnsiString); +begin + //no session +end; + +procedure TROSimpleWebsocketEventRepository.RemoveSession(aSessionID: TGUID; + aEventSinkId: AnsiString); +begin + //no session +end; + +procedure TROSimpleWebsocketEventRepository.RemoveSession(aSessionID: TGUID); +begin + //no session +end; + +function TROSimpleWebsocketEventRepository.GetEventWriter( + const IID: TGUID): IROEventWriter; +var + lEventWriterClass: TROEventWriterClass; +begin + lEventWriterClass := FindEventWriterClass(IID); + if not assigned(lEventWriterClass) then exit; + result := lEventWriterClass.Create(fMessage, Self) as IROEventWriter; +end; + +function TROSimpleWebsocketEventRepository.GetEventData(SessionID: TGUID; + var TargetStream: Binary): integer; +begin + Result := -1; + Assert(False); +end; + +procedure TROSimpleWebsocketEventRepository.StoreEventData(SourceSessionID: TGUID; + Data: Binary; const ExcludeSender, ExcludeSessionList: Boolean; + const SessionList: String; const EventSinkId: AnsiString); +begin + StoreEventData(SourceSessionID, Data, ExcludeSender, ExcludeSessionList, SessionList); +end; + +procedure TROSimpleWebsocketEventRepository.StoreEventData(SourceSessionID: TGUID; + Data: Binary; const ExcludeSender, ExcludeSessionList: Boolean; + const SessionList: String); +var + i, iEventNr: Integer; + LContext: TIdContext; + l: TList; + ws: TIdIOHandlerWebsocket; +begin + l := ROServer.IndyServer.Contexts.LockList; + try + if l.Count <= 0 then Exit; + + iEventNr := -1 * InterlockedIncrement(FEventCount); //negative = event, positive is normal RO message + if iEventNr > 0 then + begin + InterlockedExchange(FEventCount, 0); + iEventNr := -1 * InterlockedIncrement(FEventCount); //negative = event, positive is normal RO message + end; + Assert(iEventNr < 0); + Data.Position := Data.Size; + Data.Write(C_ROWSNR, Length(C_ROWSNR)); + Data.Write(iEventNr, SizeOf(iEventNr)); + Data.Position := 0; + + //direct write to ALL connections + for i := 0 to l.Count - 1 do + begin + LContext := TIdContext(l.Items[i]); + ws := (LContext.Connection.IOHandler as TIdIOHandlerWebsocket); + if not ws.IsWebsocket then Continue; + ws.Lock; + try + ws.Write(Data, wdtBinary); + finally + ws.Unlock; + end; + end; + finally + ROServer.IndyServer.Contexts.UnlockList; + end; +end; + +end. +