From 60c6ae52d0b32e4524714dd591dc63312861b62a Mon Sep 17 00:00:00 2001 From: onyas Date: Fri, 19 Aug 2022 14:37:31 +0800 Subject: [PATCH] add introduction --- README.md | 6 +- core/ggrok-client.go | 1 - docs/flow.jpg | Bin 0 -> 80717 bytes docs/introduce-zh-CN.md | 173 ++++++++++++++++++++++++++++++++++++++++ docs/introduce.md | 173 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 351 insertions(+), 2 deletions(-) create mode 100644 docs/flow.jpg create mode 100644 docs/introduce-zh-CN.md create mode 100644 docs/introduce.md diff --git a/README.md b/README.md index de1905d..a294384 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,9 @@ //deploy your app to heroku git push heroku main ``` +or you can deploy with Heroku with `Deploy To Heroku` button below: + +[![Deploy To Heroku](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy) ### Client side ``` @@ -17,11 +20,12 @@ go run main.go -client -proxyServer yourProxyServer.herokuapp.com go run main.go -client -port 3000 ``` -Now your local server is exposed to the internet, you could visit it by http://yourProxyServer.herokuapp.com +Now your local server is exposed to the internet, you can visit it by https://yourProxyServer.herokuapp.com ## Useful docs - [Architecture of ggrok](./docs/architecture.md) +- [Introduction about ggrok](./docs/introduce.md) - [How to run this project locally](./docs/run-locally.md) - [How to debug concurrent issues](./docs/debug.md) - [Useful Heroku command](./docs/heroku-command.md) \ No newline at end of file diff --git a/core/ggrok-client.go b/core/ggrok-client.go index 7ae3d12..dcc3b20 100644 --- a/core/ggrok-client.go +++ b/core/ggrok-client.go @@ -104,7 +104,6 @@ func readWebSocketReq(c *websocket.Conn) WebSocketRequest { } // deserialize request -//TODO: change to config func socketToLocalRequest(websocketReq WebSocketRequest, port int) *http.Request { r := bufio.NewReader(bytes.NewReader([]byte(websocketReq.Req))) localRequest, err := http.ReadRequest(r) diff --git a/docs/flow.jpg b/docs/flow.jpg new file mode 100644 index 0000000000000000000000000000000000000000..804b7d9534895ef2e15094a1de8a0b1549b3a14d GIT binary patch literal 80717 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 z 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

c~Y*|LYb{pFzlUChNy~Dfotxd zZ<+0F8LXCgqfr*EjLFkStWUXPcwIH5Co_TBVMxZ2 zy4LGW+XbLOGh&S8%Qw_uQB#unTtUYD?m-s;!L!sY#*}parw3Fxa|I6% zon6;r+fxkgNakqvpbAJbuIkPA`KW*Xne>Vu`C&?*(GSKFW+5jMl9nt`xws@l`Skd? z^RJSfhi}M5;O-}C(V4QD7>2%@di9b^T%+B!F?Vpyd-|;8!rBnsoiwJfoZ;?tg8 zjfUqnP0Nm6yRKMzeaSU{=>oXreY*L5ZO(QJJN%!0aKQR+B#WM?E^ zL#7fr_<8hMzN$Cn_zh84+;Ne-@|ACV&_70{#hLkxnV+MLolON7x#@NV5=QKXWAsx8 z+qdF5j+a)XgGX$~rgGNn&AZ8lM=Has8Ul`3+D(gS$Ub1}3sE}{&Kwh+nXRiULgxskanzNC2Y|ArZCUj7=p{IgjJ>3yRTp z@H+^WU?4~aJkFHEQkj)v6;mfu!q3wghsb-Kf7Pb@M(IHHH19*T3BX@pI@Y+_kgE3) zWardNRIPv-#7S|*F%5qjcPs1Us-4qHiL2O!a)4bTIe@DMs#aHmnCwAOf{KG|JgCE{ z9R>A|0{x3ttdJdNuqr7yP%_rgnnDm6S5P~YF*u*`TJ)|2f(blf^#_kKTNHMaG=N}p zRQ)R=1E}vXz=4X3+Z9OSsSF~Y62^lV8PkBm^MR+3>kPp!yH3IrQ!!uBw^5r$!Jv9% zVzr+HJOmGE!_VV+Km|$$7ES`F{LuK@$Q+*wRI^$5;~o>^1U5Lhm&K`o=esl9f7kZk zv2Eu$LO}I(Ds3=-z;r$FM30(az!q_>zHDpgj^g%*#^TZ<9oIu`IY*_F+3!bAJ~}L9 zXXjh+5rq@&T&|x+3~AM>sTB7Iiv)z%f_+*Z&1CiZ>vO(xi(?)oI+Nj@iBl$8CJ&Si zf$TPXqGVAVIJZg%pF`F1fgNsK71Vax&Vj=+Q;`)?6Wq2d+Mp9GLHvw-i@Z-1T-q%Q z-&DDAc~+k~EYI$roG6)UQaD?uR|5FAdT z`u*Sd$h9&TUEO$JcV+EbhBOT4hXurM7G#kC$0ML>{yZTQwG7KbE#z`fpeB+LEO-UX z_EcHp+F()>op)BingP}?Cwq8U6*k*SE*=4U5-mf1BW#({#4vL%M0JBma|?5Y$B8Hz zbRoR)!>(n3u6dvQE^v=8nQAOy};s3u~uWHaQi&+lgn{m_mhB^W!R} z%a+@xDav~){j+gVmS5afo>Q&QgmQ9f2X)wZ3XUjUK3bR{sxUo{Io~;)JL$i?gwflW z>~B8vMP8D|S2CBtaZ#6RRoZ|1vWKuMIKfF2e*k(4)riK4 zlr0d zS?li=;fJqZFmDn-Zbf+PAtPK_iS@tl_}t%FDDJ$vzN5cwqJwRgU0k5hw`|m8TWVn^ zfk6$_)7Px1kbs2VU4jOHk{HJrzy|iV+zAgiW9XgGFyHY5i(_(F)`DZV4J(Qg#~6+u zFn;M1`iWyyC{qfhre>zBT+rEh7ful@fxm`j#+goPg?XszWb=BkcM%ua+@YOvSHvz7 za=5grP`cHJz*B*1=HR5q?+aQ=mB@V9eOC3QPFlA9x?K!gRp~R2_2I^AzUwU=rt=90 znQloQVLq&|dtyRAyqXecGdYZ+2~z!h6|a&pL}<6*G~wL%Ttor&PMvaSKQa^N1jRVk zfF^=ft`p651?+fh+`DPrd#XCG7W_BVUoNmz9*|w-e7u^EDLAU66~T64nqpK>vQGYI zrTp$8LgxN7yuF-L2*lJlQ52jt?OFN1>vR7MUq<%TeM_^sVCM*z12fkH~{YA0_2+)4oiML zd?}LcE!sFwO^YL-!pil7-*8oDhxbSYJoMvpx6rheG(+C_P-h*pQ5~dKMv=w06C7J! zglwV`-WK;V3wQBCVNZ&d_T2j~!eymCPHAUU^yVJj`d}ETeB!#TkaT#&O64)|oCwyp zvO1%k-nTN@vt3RLt$yYqWh+`y=@NN7uExLM2%1ppgQRVA%M4=1uw{kG*ZNX44l}WO zjWqL+VV5IlGqiKxf)`HE>V2jr56ZxLOx^EnSc^1IYS@$1`kXKOd`NrhfwyGM*4t+0 z!Zv2Pr_JvVWRUMUR7IQIp!IQX9zGr){iW2e@6`ps%i(_CetX*p6oB6Mia3;l2vr z=9dY3#zEe=Dy)_c=4Rvbu({Ri5-BuxrG3hz0?3tT$I;{L{RBVhd_grwOI=_g_AX*GV>fOA|=Una% zJ`r}$1g{nbUK9D;k>T;UV}{HVQcqof#q*16rG}L51@~1&|26k9)AD@hM2E-A)m#p8 zk{1i8-)m+(3S^J)E-3N0TX7xPtWqZ$DuIiBKMM(X6(~7_exT%6hAz>pP`WpB)J&$F z+&gi_jYad0_kia6UD}Fkd%8O$px76^0s*xqbK?gUQ4WM6kdf-H=Li|CB)}E5i}tvV zg-2L}6U5!{=~3?{P<$bF5(mp~q!#Tw_eetH{8!Eod>c|rn4b%L#?}4w^6IFmOofFO z!R02L7L$K8Oy=&nGslcPA7+mB6n{x^)!Z6e(wAG-$BDVPWK0!=k6jbpy4LmZq+SqO z-tzps+e!uHiGg_@@1YOy!E}`r{XyAjwypGGpTPn zpQ*o+Co4d58k|!48^30X>E4*JcWz6+Yq%r=dwjC~c#D$1<;-)QX~fD&)teIoYV3Ff zW=0K$@u7dS)y3wRYP%Cjtzo1wzLCHar95Jhoxl5jS)#$Hx#{IdeDT#azdXn8_VNv! z1~l_SxFG^PG!B6-};-t zGtrw&x||z=wN3t;oSosg=4muuI29Tsjj`Q>Eu%5_E%Ta(t9#I&hKgjyMcb@Wmp-hy zKRY&^i-}oLA*5go_aM_DA%LCGt@8Nloy0YA2h)2yagtLSEQitz-wRh!1f?O3@sxxd zU-TXB6aAqjA0=d2aA!BmZ-}S3HqJ$B3rg|PyC1byG^A;z&~Y1Adej!0-RsxecON-= zlRx|`-d6u`fRnClWQw8Q1;6ddys4)N@q<}2zQGiP57i0*!CTaodVOQ!g)(W~PV98| zpV2l8K>*u<4~XvSLR{RDA{0a*n9$FQGs9O%#oH?4nOXc^FOv|u* zDK6+ZY$$ch1@o~E7gdX*&WN(Q7jeijvX4CS)aT|y5wy0w7r1xircliYUc4dpTUmp& zq3_$Oo^vSh8k}B1b)=bB3*V|P2 zEchPyzI`m4)8n&v`g)^xd2^yc3W$OYN~TkR|i*;XNEV#h!nlX-Pb68 zzDP5p?QoGi*esf^CG7L#i+6Z$kBgKeV)#`tr8#@D3%l?;s<1q#V6Gc2j0yfZoXH2n z5?V}LpJb^dbqvPduVNfAQNk;9)_L#`nOO&U;v5-E3Y@#{b*~O+wu_NJWqX)=$S(eE zaa5c(RJnixx3j0=ir^9I@yWfZh=Ac7H8_iU(%jlOWolMWr^n^4zn~=(7w{G8vrYay zlXu)ZYQ8G&{>$9{C&O7&-;Pe^A86hSV&m1IjT(@j74P-RTFsSpWRt0O7R$}}iol5} zbj;w77s~`C6jgXumB9sAzSX=&ze~(<#!^j!<_)mHHM2(&E{SntW{EtdTt=rP>1Tph z@JR-Xmv;LJXGB?w^O0!@y)=k>C@A11!_2Ttn3Ax z2A1wDcJ-OZR#VSlLt>LUb0=%It&bGc_Fy5+#V}xE>b>hcE4qaLkj&<2{J2A0u;b^; z0g~)Ey<=|LUQ-7HwTv^rj7-1w>({vPO|2IjZHg?~UUtpGNz5`NMGyr>Dw7OW8zNXA zJA1)0X;@{dqQk08_w*kS_E z)5?Mq1w%a3V;R_uhN0jxwPYVx>7)J2m_b6uih#;7XW{YL=63vx`&F!_W*Ed)a?)g0 zSV1r^uK!e2V>JWX!P`-e0aNtQ!U5CLqCNWd)&{=z)~D;)^Opjq?Db@~gPz8^Svlto zEsw4S;671wxY$2>$0k55T6yLY8@*#)X8G>f@>5%PE4S(osYZAxE`CqVETC{5bqn;n z&=M)WIx3@@V{$?I+&#U?Ye8KxPeouzy>BTPEz>=%TJ(^hW%qQv9*b`7h&uo2R^W#7 z6H_s5`X^0B7%j@mb_+s&CDWuV-J&%&Ayt+BIZLYP=^__qRP8noE6K!S?0LfPfjxwWb0h7f_cOP6kEe2u$3H==WBreXEg zJ@s{RCga2>VWd0=tN_9psSE|>J|K#g02 zT|#smd7(IPS7LrUs}4L-BLVj-m0ks)>yo-$t}7OK@b$j@BI@IyI!+f%hX;@$G+Fg;z8+5~j$Mk==cF=vUw%G`xed}nuV%#6b|{-lG8u>13dU;P zLtNG$0*Ear)`Hn`oayw92pK$Go@Hv{kaOzXy9As;`G6!Oq%a}g$*lDPY>-SUpjA z<#3ae@1=ZwdJEi#+5)9L9%@O7M{J+o(1?$YNfE4;+@y^J z9*Cuf3Wbp~HFMIw&Y*qa2|!=43IF$P(5WJb-KcesaVFFG3Bpukxl*T*&5^3(3O!C&fpfN?p@Ds?QsDT_Qx68N3Wy3<_!@U6Dhs_3*;xI^A*p z#l^Cr-Y&&tktgwUNaTAJ7HBN@&7Dg=2Dg&zsp?384SX;O)Xl+`ePf2;(?yCI*qAAs zJ?jkV8h(do&f+k>WZ1-2ilY(}GGFe|Ytmp)gSoY1B82j4Rm4gbHn>g~8BSz>u8Rbm z`~_!3IBKh7j~TWty?^`ZYY3$DMkR9oZes$_~(yjBJ4eebn6f$qID;fru4%~hsHqHuF~AB#8Kh9_{&^}+-LRF zHOCw7Fhf9p1?_)gruJ8H2Kd|f|4(!T_)-3_1x={?Y65-W=~NoH)jmvkT+KJuPF0Hv z_dhZk&}mwFRV2fomFhoWIcR3jfqL? z*8$?RkGsqxWM zh`7@>#ZwAG+#5J64p~oB#EfN4!q^5Ha~6g^RwgF-_0TuV%ZZzs?B4$3bv%>-;Ah8b zh2z~OtK=GqtkqE$yGl~_{dCh*GLpHBjvi0-yw0zs{?Ws9+JBia6$92GwHQKpNEAnc zbtq`sX12m{n3p4@i+d0sZ6z@o-fvBy5PM0H&qZ;6VYi*XN9~_EYJY&^T)|U>%lHt- zIl^Ni?Gl{T=kjxSA$It>ZUK0x7EZQjfqnY{nYRPLYE|wwXmD#@92zzlvdx8 zIVgN7Gm=k7lRk?|@L-7G=MX38#_$#s;YpA_hS+?GkVaIIb3t$tMc^fiM@JxY&V6wY zv4by&lr|H6yv5Ct@y*rM3x+I`Y8)ZMf z`>h|nnQTrt_ub+ZmI8X;r4E+WAxmWIPUudSMI3PrLUj8)J1O&3N_Lv6?UBEjm!Sqx zNQjJwUJXSE4OYXS#)dtcLJSU1H$H1DYm~k7k?$FPUGg|`5S}H(?$q4oW)Fd-v7 z5fy?wFnO0gPAAO;6tJZ_E?)+%ycO_EU$KjQr5A=mS)HI=+CSHbXXQLauAh1+_`}1%csO*nNqdwISq%fv)FKYf@dtl^^wqAw%Ir=m~xY zP?3AmO`QZlp2xg4CYpdA-2X0FC3~z=47@#Vzcyq>pDuf_>~Srvg@>ixt>;g2)qdM7 zIlV*^;gm`1UXC%d^xS_mzv{282)~$%e}nqZTJG6@i3$6q{lCD3-hX&!p8vHcF6J$| zlqq^!G-r&5#nMI(s~y!aI$Xk<^r2&M?!6;>g2s00XXGCY$vVdi-x${J9mi2}J!}@b zZh5w?p8gFD`GM<9$dmnRIQT?;SFqMjV*`8*7lUml0cUMMFH?h=Uv)HpvO698$GC9r zyB=Xs6vUoXNcEv0&0aO;0NEy!HrKg;Ss#G7$%>#2^72-rJ8}z}loeh_r0EN_bXw#E{yjDMyU) z9q_RB$S}h=BJB1f@1p$eD;{M0aVuOk1&g=!zDW7rR2w$#p4ZJ)8HjTdxqn+IRy!@W zH=M#uTi`)DI%JoaftrX#fo?p~vY{+^9xPk`lPTG7o`Yp$QFck@&y({vlq9pl$JCm6cgxUi`;(WDF zdi?i}ouEJ|5}_E`Xq2rpxb1)s>zbE;NFHw5?!V|tC)zn{Hlo%Qzx^zKSvb!A)7oRl z?rKmI6Z#(4+=ynzU&IV=oy1u=$`lnWc~zJD+Pc@;C0%J{%d~%dw~2j9lipJ?gTJYA zZ7VHxYZ5oPr`C!|X+Rel@$-FFRV(dJOfpHnb5ZAV@|6Sh20IO9yJ|QLMA5vy$C^o zOU8XRP*_Gws;#-bb%B*JgciVi5niIMuF>LAxHL@s)LOj<{o?how9JJuu^!pY0~H0A z-zPUt`%xa=o6?@$D7!n$RLGi3vD;6ifnsl1d)%d(Jh6T2n=TyYoH~?TfESJzl!=uJ zb-HaHVSf0zOSl7~QkirwDdldLl?(HQ1RI71Z2ggz&4__i z7hqS*6l|4Lmug8Y`@1Y+2NH_cz@pB47Fu5|xq&!7HhPas&9K?otJ&WO_HKawPUFq< zd*Z=^NJg~FM-|i3&u{YaN>gZtx92U?znrv4xzf?Xb?LHp%A;IfNkPxWa?1?_=KMGe z=U-0(-l6I}a9T`cize~bquS5xDw3!4mCIM#>f7gETsRIv9t`jstJeZ+^Kt2OfP&zC zUl@H-U!P!z@C{`<;e-JCj?(49CS7;crwm4;XKR^y zrf0KVN3}jD#mq6%c{@|j=FGZQ8?%;h6Ni%nvIlmz`f#tB0A2~x{J^)0*@MA_oA}J_ zhT0QPvsZ<20rka82e?n9sOa9UqO#G+1F4O6u0cfZT8t@MBT*9^Qomu)gnMyTW zIN+4b{`{-r3!iqALvm6Ls)}=d(sB|zZ|LuOWzq*XfcCq$%D`2SV|fLfM=033(2m)a zeC|PXh>+*bQql2T{PP`;2`@%GHY%612@{?*^z^c9OT+_UAzQBBi*gd5|ev1cXk z<;m|kuQH}xt#*yN+*S3Vkw?0+$GH&+SO{ji?P~mZZ?5v5&z&7_udKkV0os9^s7I(V<$dAOY4Nwu$k& zpn*69R}Y?QB@tg zvKG z<|Yy-(?ir~2(bwJf_reF`f$)q+^uOob=}IIYVoC?~qn~|WjQ^W~YL*=|ycb7(Ml7eIiP7&1dWY~CpmuatmIxZ_7Y31l z-J?@z)4znXKlF$Dtn~ckjvo9Ecl0D=V(Pv-`tAS49sR=(x$owGdKa)?9zOFA%r;qC zPXaHzdxQ?SL2TUsbtJ=)1oQv4BRAQfAfCg1@LxQEuKyc$fS3KKS=li@aIjwo2fN}N z+9Y>9#R}wqHO`;dy0p*#WP;gWg#SPGuc!Q5@;|E|Ju6$y`y2jCu4h<(=f4NBbtZN9 z_#E1J;jeI!GxSHD3I1$F@T$c|H11!{%hCK{6E0|*FW(Z z+=zYWwefB)G9maNIQ*+{j>OiH)LpejwDDg={QDvI0sczcza{%~u`vA0wR-rL-V8X@ zw~cpmkO|L0XPZY=0mxt2Z}b z{I?*seo5Um@I}5h`cJh^;Ni-Tj`QbUMwa{kvG?9_O|5OZa1ap@l-{I7=~5LbDkTa6 z0wU4{gosEN5CJKML{K`2f`Wj6N|zFPhtQE`=tz+gK%@i`N(iKI)}AwS&VFalcfOf- z_MX}AZ}uPl1hVq1^*r~zo^s#UeO=SvCoOfBZEwEPzNLJlMceSuSzKCGRr=|g zvBFQIi=d)}>Go=AW(^^khGq3PGy{WXa=wfwl~}#EThOAnhUe-h+x!l82bYAi__DA?mwzASQY(rFS^D_ zeFrahKj9DTSrN(#H~b`+F0`~Y<|TG3aeXP@qt=+@rDOXv7N1XR)gxA}3UG4b zxXB!G3&?i#!~(%DyleJ^qQa1G_1Jo04BGSLmo2fEj=mFDt~@ph=+t`3{F0pzQA1q8 zVb+=_Hsndw_qF2>;2 zlwSEQraf;FR@#j%PUu+n;BF&dyAs1^XKup5+c&inY8Ckk1}|%TL=Bg(fJ@08#tSfNeWh9w$VV~lQU@!)e^@=Hm;6hoXTKcsphU4 z8}3~a?R)jnH}28$kFI+Lpr@jo^U!jqP6!bSp@T``wK0rO`LKcA9v;%zAKQr&6UiEE zaL@S-y1>3BaolJV5*)rfS|mIw#;dQLe&JEu$aQNkG9Adn==R<3)|OZIEaJ0Rx_VV( zI8A*P-_uyeVGK`o(Qbp^6BE&Pw%BfwhXOo-VZ29!B#v}& zMivlL+X87@xEc$rT#%1j?pr4>y!g`f;p)0F*v(KlolmDV<$Bu__}rdf*!u92n=NflS?sP)mw?;) z>OWkdgItUUle3pwQU5ff4Mo>cjF3G5p)#V!7AAEFda!Cpk*G(Gp7>LA>~{=k(QZXk z8G(sgEDng>+bg93d>bBaNqBKGTig7pY#bZOYE;%&A06}91^L+!V~H#l45*BgLkHm$ z!sKMKdnmK#&wZs#j{_yPbguy#SRDY3SekkZSQC^Lm#Jcu8i+F<7X}T9htc34LU`qT z7P5k}rBiIq^sNw{pu&7-8!vBjc(bTENHVf8$2F+|Om#Y)Xrd_nGuX8S{jQP9MsVlt z6>jCn<*L1~3F3`!R zgrG4h6HJ{*ivgxB?nzIwsd5rQzU!!wwL~I@QSm}SzruR@U9Y!nEbWr)JANy6kL zC!-cs< z*X@ovM`U*8SSnp;OJg~sQ{LHl%$d}AQ@^WU=QR>}g@qc$yHHhVtoQ;l)T+8U_97t& zZa{rH_g$B? zP0w8tYYHI(*<4ar9U6NGB3<(D)xphV2hCGXweBmP)MmV#U0&|^@?L@0m5(olba_s- zUFKQ43cf^EBRT=dixy4PLugFha+js2A~YOeYeb?-c5Jw$l8dms4J#kEHCunK-nFVI zdw4cE>#FsA$?-7=2z4LD0RU_(HmWhm=XKeQ87+ovD<5Sm_@aB0i;H%T(%QF|X|Jvp zHmLMBNWaWg(T4j0P90hf^ovZ%g`XqVLBa!<+vQ%&av>pAY-?AmNH$+H{2k&QU*1Tf zIilG9`q_H$aIeGTo>jFef+HS*)yIcXB1BF5EPAI_KiEb_C!!}1X)R6IV3*paeqXXbLh*tsEGx(8u!?c z&)&~phCREIF_A!JsK19N<*Y`U+=d2H-_@9}+p`Ww`N zpAn>2vK&kxdaU?*^v5-zpe%{XMMSg*26evu>_Qb7{!m*{A30YOVj8NcBk~yZrN>{g znI&-ihv&LC?jmLg(Q;W`i7Y)vK9~Pf@Eo3Qqrry)F)dHPs@%Ww%qJ}Qkb`x44Y z-IxoQ8=zO5D?l<)yAZgWx^NZp+0rXOeXGdIqD~&ntIVU3hr!t?--I zNfu6>`inoBN{Dv2?XE*mr8t>=){>Iwi#Xy2r^Uy$tyzndrQWT0s&AmZ#TS_qD>LJu z*`u@6bE93`?6Tch*K^3jI6i;+`LI-6K?kawInt9V^E1(C#jDUtvMKecmq(Mv-HO$0 zLhWtrQs>WrNzx5h&=%q->=8b^(~~Gt-BAw}ST%ODLOz#ezoEaMmX-Kp(5XD-*z0L} zN29YhtNYAMOS=_!4A(75+{BA*V0u+4lA%A!bOd${&uoM%FmzGalOb5ksIM8gJ$1YD zaii2qKDX}{^Y;@VTF=gmlMt6!O)5j_LO(4A+mJ3wOvN}4t-A!?N_|*9)jn=MWA>>f zr#ZxuRapM=i9Sa84asNY69A@h=cJzwC9Z*fKGynHZP}PKnLXFa&_PvBpwEIKS4csQ z9keD?GI{&iwYiu9`h`OfnLV5!K(rxUCo}z0ijMzbpxIs*RJps6@yP}3SyNS362lR& ztQ9=iFySD6MyXFinrFeY5Z*Q;jiMxbpxlt*V2=!QTwF_Xv7&*{aX+2wV-*6jY8vCWtLxq_pBPpj z-248HrEmU@aA)CrNKHG(_73u}av1&2YIkvmuw(`bu|5vG)^axIVhA(vk#`LKK? zMZUWCu*+5}sNkrhgm7f7#0PM*X~l06)bxx+WCI+1xGF$Ar#kLCWi7`>Om=XWZ`Env zb32mws=bl$Ot*Ly1!!sglGkVcbzc9?9~~$7uWFxW-yY++uiy3J5VZcj8xTTJ_B}|0 zCfbSt!IOHTKDE0&^UnqTo>^+5lx`_LTz(V4UVD3kj?VCcM<-jJA&n>E8zGjRn5 zc$rXij86nz|Bv{DkBuS6SWDt>c-Nd`*M6*{1GNf<%aY|hBVuu{VJ5lY)6%wHgVccx z57vFS$ELVS=~5a+eN#<>cF?()i-bY6HD2tR^lR^74am_+bCR{n6c0=+?EpCS{Ui(>l6LR{;@{w3$}R%dL~mj z`t*~arw*LUg!C)8+?R0h46$o|qtU5&#oKDSvXPMLEzmBypiTd<^ny)K+fk=8G5n{= zeUujv05L{Ogc6Jf8Z=4zWaC)h@A@VqiM+nOd$p((o}A;^nn}r-8eZ?+1>^zw(XUa9 zs?5Mv;3(`SZe37@__TP!KU%w1y7c?EuL-S|r+EF&o{>*Kk*?!qtGvKG#Z0ip(MO5m zpwUz@=M;}h_p?z?oobl|Zd}RgOHC9LXnQN%2)bxQN%cw7A>;$1H?HPmxduA=IFd7C zPQ;s5d_{cskK~Ir%9}SjFLVUSSO&^gXZk1HsKC>QBj-kT`ZYvTW>zP)QW!pYz4c$z zFnjaz8a?NBVkXHA-d$%HnbCSWuEXYtz^!wq#ZCNkBo@sld$wa*vjmSj@qA#;bI!Y0 zTU}TEa%0Rah2hPW8!^1RJP+hc=0YwXwdnIhRduZ==lZ8LfS=+mu0@;bGRfv(Z=O5j zo^3S;U%H{nU(*!`I*5uQk5U@cMTt`_R0t)dsDS~a-!InIPE{_gL|HQ?MM8FRjUOb{KF*c@!@$$YC(esMV5)IWlEosic1>k(ovLfJ;xDr- z+3-8u5uAJe--M3;qte32wnNZVLi5Z8q60nz!scSNk?l60ahkj4g{H4$cH`;-I+~`0 zGm=g2+BVBEF4QBbR)`Duc_NCxiET}g;jD@A3E7GXYjAzzGj_1}hetObK9x!sYlztd z;duhIPYqdRVjCcsOH0XbuT;KW>D3jV*xgqPlC8@??-6oiAU|D4Q8;-h9$AaBlZ&&e z4ucEbg`LHB7AbC|V&3cL82U-w7=(VB>u5wDap0jPtv+L_MK=@Vo_w+v&=U_i=+q9u zoTIVOwO6@wUNz)+i(qZ8P4t(((=U^xtXH|%L^~=<(~9Fe{mvO*KFa!fGm89N_N&x< z>4o>HPXaS0rwv+aq=#klHCXhMZ4|1W=I$MWUQ_t}6XwuuhTJeM9J(We)wy-M1Aa<( zShw-w*RTl>)2t8L@zRF1cjD6crZ~5t*lqmSX0!T<3Zl)tR8=)zJUZ+_yqJ_|ReEgr zBb_rn&7I79nhQry5Rf6z3ePb3Sr{EO8ZK1-vU^9xk{L;YuepLzkYfl$&FO%kn{o9}$v|hakR^-s7B(+#+A)oZ} zsR61j4%s$Y(}2{dRE6 zhiZqgwp6#nl^7mrGO2P2+>1*O-Tt9mdR5!JNjU7$XsQ2;Z1J8-r_Cmb;K z7O)OkK5Mf}sQD36+|A)4g&?J2-4f%(DkGhvdUtABJ3pFo-Pdgv&=uJ$7%_sDWg(dK z4C+gOq!r75LBi$#wV(Kpd`7d+N79%?w7vy!!bxOiVlVfB`dhrx5Q1^_D08BI2@}hv zM@P!jyR_pymp0?jd1OOm2kEXQd{%pzdhRJInw_ZG=Rr^Kn@AiLJ2@Q`QM1#tAbbpU z4>k7*3R3mMZ_nAJp<1oA0dd$DyQu>9I(L@C#!N&W-hU%9+~Acn@~XEt=3S@!H~JPu z@VD?z6GY2}B9xPa!W++s#DScWs+^|)%F(TIzAhdN@qaGF?e>W3*mrM3IG#pxUbayE z4E+0Sg&$V*3SYnQ{IQoX<(z!FBrF#`vF^Bz=v!d2sj zI9X?$B;PB$KldDd(>bW!I!lk2kEE8eLlHy}|Pn$AK2!Zr7- zk!)mXq8qMT{UTXm#FxSMB-@XZdMYaQ7a=|O-(M+zAbIwY5JSCm8LVA`J{{hkRF@Qv za>~ z#))^LK%45)FdbY@>Vklq^+<6Mi)$i&M$^UJt@4Nt)5q5j9Hp1=^>kOyoPq-^mCF$ezK_msD zyk{_?dbulOB6HqgY6Z?QMwVAhQ=h)5E0_W6b+xmriRTjb6MDG>EAbA7N|5_D0`aE6EpZ$lz$4q0xjfg#(~!s7G;uNgNvcRtrLn_j^Owfe10UuNvNv@9l{pEx!c zwQn&WPG(1T37(<6ri#HJ#B_o>55Bs+|E_=7WM|)VyUe>&X@s%m9{DpG`d#8l4I&SZ zBubuib_EbjOSMtqb_?I`5&pfF|rU- zRdL|bpOp8W5JyIEXf>R5*baXx_mg(He`JwRhH12MIt%u3k_f}21c6?bN1>pb?!|Hd zrOz!1X_TFT3Bi8UH^MMRu{X1L+SS>nD8ZVJ>7EB)pA^3?ZCb5e)76g=w7ud$euRTS znnRN>>cd1TtHuSJonsfY#@xrirS4lNzi7QGdDklczhlO?S68M6qb5_YbD`X%Ey z%E6tnJ!LvwEnz+*N^r&FI>bJHb+X5Q!>-y?-mttkLi)HLYo(4r0hDkd1x;wB6asrU zVM6gmWIN=@amN8wz98CbPB~VI==4_x+bYfd;8V|;)}%b|r$g&tx|EnEX&3dAl(!8h z=Xo+Z<8WOaN(abDUtSMP%!{Xa#U74ex3?m%Ijdh>XCiTGPtRFclN_mi5bh=7;GE~= z6yf%;eIqgYgP}k%GB-6sdf24YO!Bm@tVq;FQscl4Mi1u+rMXTC+J(5uT#Vlfm_3m7 zIb9EMIH2i?(HOa4IG4+9KDkpwcsn%NQ}#o3jaziVlf*Lv8#OdjPk6F!n7t}drSFvE z@t0f%%A~;2vt^KG2p>!l4+-;izLk}Mi$yrB?9~PMe&^G2kS?f+1qgtIHe;*}f}TVNdgV+*53T z5{QF4rH1VlC zn^ke~0$RuP*#`o8lwU3-YOX+eEOw0W$a%*EeCm99HEz#b2!iGD_PoG*-bQD;O=2_j zkuuHbTW9?v?uuM+21B8b_0KM{h*gzbrOPE5LL{hNrM^(=1^+8+ zZ{|kmO$+S%%Wn$B1hmM8<^ZGS$4ql}06tX~}Wb|+{B2wzU}W3Ch1e{$e$I}S~pkp$vxbwm|v zcLVCc42ZWW{9BFwSO1OXk1KqapkV;9-2x$(p@eXJQ!rkI5Q2A#A&Vb01T9EVte%38f>a$Crz>e{>b;(!uOWPb?NpUK}s-9+O-eO1nJfWJG6H{#Z@tG@z zDLflO{-V4(M_G4~UvZ;LdReZrDH%ii>Bo2))NU#e;#~kj#49d!v01`E+0HIjZN-lxhoH+f8R4r-{;TgKr#gO;isLrOJ^&G6z2Tn%4Dz3W zeUq#47Ub;V78JoC8_>6?pQPMC_Qr7n?Cx&^Tbv`rVet^E;1pRBx3i?U$#?AK{qU2g zQgYeZrvtk7>|;2$FCRUz*kv!Q@#Zu7!PC2H@n(Hq6(v4?3DuugsW-@DL@eHMVWGP7 zq;-@^S&*%gw{kAB2Tdna zL+$eBA3v{1(94zWOxoeQ4O!Tb5;7R46-ivW-z9ea z49**R#brD{&q}lhXUL?+2GumRZO=1VPrmkPU$+=M;bESc&_V>tHk^Xift70XDbP1A#hF#n~|G`9Zj-za_yH z4o-6fQ6}!Mh!|3PKRP`0u8f0O?D*mKx~_1yqv)KeS8yXu9%+SBh4@2I0K@zOp!*kW zF8E=5+xhA}v#@J(QKVif!yf(vQKw@(;|N*PyZYw1W%16FS9pPMyon&gYm&vLh@eN8 z1G_@J+FAX5$1ok0F-{I zD+;LMkWB|HSLQB9eLFM0ss_JHET z?->gzLC3-6VTPkk!mALL4;5}>tdmoe&}pklZKaMA8JAAcOJC)CUw-ew_=*8%S8TI! zyH5P)I<>y%5%>TH3n-nerJ~z}n!3w3GBBXw&i=jBsJmdIEP@JwTElx0Wp9(#aR)!j z^H*0eG6>5F?#cFJG$%jXHDuFGlQ)zvpjB?$FLfVKqZV2pT+(AVJEzefnEt~XqwXEo z*2@@$^j8~=pBe;nkndAIHu%oTO)k%MHK%bga^v>eC4A2*GB`#~Zd`O=|B|j5$;oly zHs51qf%5@KS0I{y;^(Yj6Cc!{+4P;|^u_FsxASp*O4UX|v5YoyuSfRDELVk4)<2PKsuzN9t!7{jL4A}XxzWjF%!0Jm zFm)aAB|9hQqhOkwV3CM7RQiUvziF1Igi+(`JEdka@iZdAod!0aqV|R$N(uG92y`6( z&#ZX=-QOkG;XTRO=oSRQ0I01W>Yb!$A$yTrz>0g946M5k#-J2wV8ylibF8?3F0sG= zZrlAH_b1om{m9w9Es%e<|0?A=vKP&b=(*d#4w!!kFn<$r$E?@5sX>@WG}u>W7+kKf~!|8*PWCVV(x|7M_a z$^bz83{9<4t|5C5K|H_&@Jo#Q!Po!o0DqD?gCPp}SEX+M5y;%X=i2{~m6IrZ967tK z8AZ7H;}`oMP_82Tn0XOB)?~*2ZGKr4a`@oS@&5k-th~Q+r~i@WZ^kE)vr+$I{BF%>P&2^zUKvU$^lOW&W|h*8bm-;rK9NW=obk`ymUEmhX1C zIOcXPN7SI)Xt7Vl=sa)6!+?`CbvO0vxV8N(_1TU|(9Zr7)$8AsguNxV0*RIrMsw&$ za0RplI!W6u$lf&cfavR1pq2m4T36spx^;oy4TFke`+duA9;ZHh_zC4dFnj@dm@x^Z zp5VrlG>M{fE3@-m&t#X}ifm;BEx6lmRG%$YrCgR}(E`OMgmLX^ZS_vD`DqQK!Ufc$D@k_`N!i1A0?UU^32H+jIsV(L-e~@Avbay_&e)If7<(WyUeE7Dqpgdn0TZnuCs9A|1N;F^YYFV{ zZf81JeTpSjpf0-Jaeb?Py!vOH=BI{jDi^xV^MI!avRMb2e@Iq31bvJerjm-G*lA`m zkWZu@MfFXQ)KO#-!dvZB)HeE8&Hns_ngBON4U;k?ZN&ZR3y|`)LlC-p@8ZBur0U;X z^y_a^zXPF2G!ZeUPj364eTOw595;!c6HF!P!H(e}t-aY=BeL_PTS+QA)gwh)gw6CG z>pc%0b?cH%+tfo4qtkpfg~>c)?>iV*1?NKybO8J*L4YW0FdzX02&OhC?S~VB5c>q` z55$Sds<-*hRz*L;3#Zi5sA&p`dn`AU8cZ}g1bCh}+`A_D36(ob6oIaPKLnkqo&zdF zc~!OCmH{xARMO26=1awB~(yi$ijFiVFZT;KwSCOI&(nD zeKlO7Cdy2!G-deMPr_NGFyqoI=FQ}Z8sv1W3dXQCR@|^^sG`xMb?j@8NZ4`vd-?oE zd8S%M&1Sa&Mbb=?A1#2$01)HBa@)-72Zx|-ETwPfKnne9r_B7dvsOQXY*WERwnNZ) zDjInRQlFhh|34i1|Kko<{<_28jPSp0hyVB}l`90f>5Z?Om<$#hZ;WYZbo2Ja=hD-E z!&~{TnB?X_Loeu{J8YI@Nv0h}xEMa05hq&uhho{{VnRLteq`P{g%Q)WM`l*#x zqW7t}DMmiH@UJ+rGoNaVuc|U9Hoc`h2j=t`Xtf1ai@Rep&T4ly5~hY5OfcDUQh8`{ z`w8TJ2%`@p(>?j*RM#ybn(cZ;;zHifl%|BDyF_oJ^2Axw@|vf*MahOKNvyT8nNs7A z?wQ8fN|8h`_VF4$j+`s@i}Zwb;GN4(kqpfawiKuB8|^EU1r2uC~A@-%!AY?)6+Z(f%B zo6mK~XKYUZ-4V#3>>J{WtD+$P+%|LJ2-RutebV22p84x1v#{0zPrUQo-vigukGUc) zlD_Ky`s3d|c-(rQGMN-N{d`Rkl1y0!lF7gBnFIiSW^94((3T&9Q0J(eV}m*bM?Pv$ z*T%Kol#D*=cKqL7z=3F`n?oMZ0E{lPZ-7N;%{h~F5lBa>FYKK;=r{a<2n0_reQgMI4dsjaUxWUS zIOwLk;3GsO8THjWJJyOmkza&3=0Y!FZTfGIA{My=4ncPx9D)W69IR#9wjkfTJig@J zceQzn-aJFefGj4uAa;kA4?#L=PtE+1>-&cwk#hY*P;nCE;PTpJ!J5B5Rm&fW(>Vm` z>hvu`HhmH8!2aS>l+;hd^S{SI{O@gpyn#0)bD-zIFJMN6M1AAI23?sS;ZMEnR5YDj z2YKDIoZo*`cGXKX3AuV{U5b$^m`hRtMuD*dLZk(zuI{({;042d=?o6Yy3;6P=d)Em z5-VNZ{64B!FwOGZ_bjUH+5p*`udAlVEZaad6LJqVzltbFQ9unQfW(y&ikz%NRx>2{ z0TT>&6asf_9H85i!V{vfhoA`BS5Gpibb#L167Wl8EY%d=f@lMP>rRLR8vk72@9v0w z`8Z-=#`-2Oo5^tiL~kd7{hFA?0+s{OPV+^@@DOwaxCWuF6pdu|7LW6Sui}G`t=aBa|CZl1x+4; zwjAKAT7_%N6w>!{2P~oal4R-S?V*}bHH|<4@$j>?3x6c{J(<@ z!3fzS-U>d)cnZRMZO>5ZDmEwmNZ`BB<9P;Fd-_UBR9GFEUq|Re?G`!lUP7&0O;6#8 zSNq^R$iJ^;a0&EZUzqt@w<40Xz8CiTpz zYm*2fHyQMo>-^sr>%Ycz{yYY5`bMa8&KCw$VTL>z_%Pm*9Lx%PI79R z0UKnSWr|nAA?TVSJkJ%i#n=F>lJbb3V&z3R{ec?T4Isd92#UANJ_L>HXHfWev5J`4 z@=bDlzZ|vhE-+CqBVCY)7pO&xP@p0jvGWE-^``DwkPWwi^Ip@7?IXW~w9en#2J=#M zXU3MLx&-1Ix&X0aTA6GGR{%QGjn|}h1_6fyz3jpsKyy(ZvL^|dhrT9v12LCI(_K42 zcQ!Ae<@YGOfPMxzGd=fNn}h+`(ICW*0o=^uH=g~HxCZP`uuwd(9nsVI?jJ-277?e_ z??3@aYwpIi-{U#{RU3bAox@-5{FgibDc$++0}}k@&VRY{pVFQGK48Qj?#`TB;n*(B zCOeUxV1@^npjG+u(V1>nf*K2~zfWC$=^QSc2XGSV>5$s!4$q5&QvFitQlMee1l_eUb)b^@P!gk5_!zxLZFZuXFM%3yyvx`?ctqli1VM{L3zOA2%gbDk;tmr?&>X zozbIcUa|qv6Nll+B|gU#Nwr;=cIt7zMPR%jc1cBb*=9D>jcDJ|#DT9$5Jb)OyY|`+ zi;9SjWq1pWFzMVDiA>fN*hjX{eyg8z3WIpacz4ZKEMw0doe_T;YYA|z8pLD#vF_#y z)*+wo`#8Q7NfwTk=dg=DX8(G(t6A>bLMOP~0;`6RlnqrvJSYuhGSi!fG8pw)}XVFqmDASoOS&E!uiGTST-!w%zZ1sle5EF^5YB z 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 C;_sLM literal 0 HcmV?d00001 diff --git a/docs/introduce-zh-CN.md b/docs/introduce-zh-CN.md new file mode 100644 index 0000000..356a9bc --- /dev/null +++ b/docs/introduce-zh-CN.md @@ -0,0 +1,173 @@ +# 基于Golang和WebSocket打造自已的反向代理 + +当我们在开发的时候,有时想要把自已开发的接口暴露给其他开发者或者第三方的服务,方便我们调试和排查问题,那就需要某种机制把我们本地的服务接口暴露到互联网上,本文将要介绍如何通过Golang和WebSocket来实现这一功能 + +## 为什么我们需要开发自已的代理服务 + +目前已经有许多可用的代理服务了,比如ngrok和localtunnel,但ngrok有个缺点就是提供的域名只能用几个小时,然后需要新生成新的域名,如果想要固定域名就要花钱,但我们自已实现的代理可以用一个固定的域名,如果给前端同学来调试的话,不用改来改去,很方便。 + +## ggrok简介 + +![ggrok-flow](./flow.jpg) + +ggrok是通过Golang和WebSocket实现的代理应用,你可以通过Github[仓库](https://github.com/onyas/ggrok)上的Heroku按钮非常方便的部署,然后就可以拥有一个固定的域名了。 + +## 如何实现 + +### Step1 在服务器和客户端建立WebSocket连接 + +服务端基于gorilla,监听WebSocket连接 + +```golang +func (s *Server) Register(w http.ResponseWriter, r *http.Request) { + c, err := upgrader.Upgrade(w, r, nil) + if err != nil { + log.Print("upgrade:", err) + return + } + gconn := &Connection{ + Socket: c, + mu: sync.Mutex{}, + } + connections[r.Host] = gconn + log.Println("current connections: ", connections) +} + +http.HandleFunc("/$$ggrok", s.Register) +``` + +客户端连接服务端 + +```golang +func (ggclient *GGrokClient) Proxy() { + interrupt := make(chan os.Signal, 1) + signal.Notify(interrupt, os.Interrupt) + + u := url.URL{Scheme: "ws", Host: ggclient.RemoteServer, Path: "/$$ggrok"} + log.Printf("connecting to %s", u.String()) + + c, _, err := websocket.DefaultDialer.Dial(u.String(), nil) + if err != nil { + log.Fatal("dial:", err) + } + defer c.Close() + + done := make(chan struct{}) + + for { + select { + case <-done: + return + } + } +} +``` + + +### Step2 服务端收到http请求以后转成WebSocket消息转发给客户端 + +```golang +http.HandleFunc("/", s.Proxy) + +func (s *Server) Proxy(w http.ResponseWriter, r *http.Request) { + remoteConn := connections[r.Host] + if remoteConn == nil || remoteConn.Socket == nil { + io.WriteString(w, "client not register") + return + } + + wsRequest := httpRequestToWebSocketRequest(r) + + wsRes := triggerWS(remoteConn, wsRequest) +} + +func triggerWS(remoteConn *Connection, reqRemote WebSocketRequest) WebSocketResponse { + remoteConn.mu.Lock() + defer remoteConn.mu.Unlock() + + remoteConn.Socket.WriteJSON(reqRemote) + + var wsRes WebSocketResponse + err := remoteConn.Socket.ReadJSON(&wsRes) + if err != nil { + log.Println("read remote client response error", err) + } + log.Println("remote client response: ", wsRes) + return wsRes +} + +func httpRequestToWebSocketRequest(r *http.Request) (ws WebSocketRequest) { + reqStr, err := captureRequestData(r) + if err != nil { + log.Println("captureRequestData error:", err) + } + log.Println("req serialized: ", reqStr) + + reqRemote := WebSocketRequest{Req: reqStr, URL: r.URL.String()} + return reqRemote +} +``` + +### Step3 客户端收到WebSocket消息以后转发到LocalServer,并把LocalServer的响应返回给服务端 + +```golang +go func() { + defer close(done) + for { + websocketReq := readWebSocketReq(c) + + localRequest := socketToLocalRequest(websocketReq, ggclient.ProxyLocalPort) + resp, err := (&http.Client{}).Do(localRequest) + if err != nil { + log.Println("local http request error:", err) + continue + } + + wsRes := localResponseToWebSocketResponse(resp) + + // log.Printf("client send response: %s \n", wsRes.Body) + c.WriteJSON(wsRes) + } + }() + +func socketToLocalRequest(websocketReq WebSocketRequest, port int) *http.Request { + r := bufio.NewReader(bytes.NewReader([]byte(websocketReq.Req))) + localRequest, err := http.ReadRequest(r) + if err != nil { + log.Println("deserialize request error", err) + return localRequest + } + + localRequest.RequestURI = "" + u, err := url.Parse(websocketReq.URL) + if err != nil { + log.Println("parse url error", err) + } + localRequest.URL = u + localRequest.URL.Scheme = "http" + localRequest.URL.Host = "localhost:" + strconv.Itoa(port) + return localRequest +} + +func localResponseToWebSocketResponse(resp *http.Response) WebSocketResponse { + body, err := io.ReadAll(resp.Body) + if err != nil { + log.Println("read local response error ", err) + } + resp.Body.Close() + wsRes := WebSocketResponse{Status: resp.Status, StatusCode: resp.StatusCode, + Proto: resp.Proto, Header: resp.Header, Body: body, ContentType: resp.Header.Get("Content-Type")} + return wsRes +} +``` + +### Step4 服务端收到响应以后返回给前端 + +```golang +func wsResToHttpResponse(w http.ResponseWriter, wsRes WebSocketResponse) { + copyHeader(w, wsRes) + io.Copy(w, bytes.NewReader(wsRes.Body)) +} +``` + +至此,通过ggrok我们实现了本地服务的代理,并发布到互联网上。以上是一些主要的代码,详细的可以看[github](https://github.com/onyas/ggrok)上面的代码,有问题请提issue或者pr,共同打造更健壮的开源系统。 \ No newline at end of file diff --git a/docs/introduce.md b/docs/introduce.md new file mode 100644 index 0000000..ed6bc62 --- /dev/null +++ b/docs/introduce.md @@ -0,0 +1,173 @@ +# Create your own reverse proxy based on Golang and WebSocket + +When we are developing, sometimes we want to expose our own developed interfaces to other developers or third-party services to facilitate our debugging and troubleshooting, so we need some mechanism to expose our local service interfaces to the Internet. This article will introduce how to realize this function through golang and WebSocket + +## Why do we need to develop our own proxy services + +Currently, many proxy services are available, such as ngrok and localtunnel. However, ngrok has a disadvantage: the domain name provided can only be used for a few hours, and then a new domain name needs to be generated. If you want a fixed domain name, you need to spend money. However, our own proxy can use a fixed domain name. If frontend developers use it, it is very convenient without changing the domain over time. + +## ggrok introduction + +![ggrok-flow](./flow.jpg) + +Ggrok is a proxy application implemented through golang and WebSocket. You can use the Heroku button on the GitHub [repo](https://github.com/onyas/ggrok) to deploy it conveniently, and then you can have a fixed domain name. + +## How to implement + +### Step1 establish a WebSocket connection between the server and the client + +The server is based on the gorilla and listens for WebSocket connections + +```golang +func (s *Server) Register(w http.ResponseWriter, r *http.Request) { + c, err := upgrader.Upgrade(w, r, nil) + if err != nil { + log.Print("upgrade:", err) + return + } + gconn := &Connection{ + Socket: c, + mu: sync.Mutex{}, + } + connections[r.Host] = gconn + log.Println("current connections: ", connections) +} + +http.HandleFunc("/$$ggrok", s.Register) +``` + +The Client connect to the Server + +```golang +func (ggclient *GGrokClient) Proxy() { + interrupt := make(chan os.Signal, 1) + signal.Notify(interrupt, os.Interrupt) + + u := url.URL{Scheme: "ws", Host: ggclient.RemoteServer, Path: "/$$ggrok"} + log.Printf("connecting to %s", u.String()) + + c, _, err := websocket.DefaultDialer.Dial(u.String(), nil) + if err != nil { + log.Fatal("dial:", err) + } + defer c.Close() + + done := make(chan struct{}) + + for { + select { + case <-done: + return + } + } +} +``` + +### Step2 After receiving the HTTP request, the server converts it into a websocket message and forwards it to the client + + +```golang +http.HandleFunc("/", s.Proxy) + +func (s *Server) Proxy(w http.ResponseWriter, r *http.Request) { + remoteConn := connections[r.Host] + if remoteConn == nil || remoteConn.Socket == nil { + io.WriteString(w, "client not register") + return + } + + wsRequest := httpRequestToWebSocketRequest(r) + + wsRes := triggerWS(remoteConn, wsRequest) +} + +func triggerWS(remoteConn *Connection, reqRemote WebSocketRequest) WebSocketResponse { + remoteConn.mu.Lock() + defer remoteConn.mu.Unlock() + + remoteConn.Socket.WriteJSON(reqRemote) + + var wsRes WebSocketResponse + err := remoteConn.Socket.ReadJSON(&wsRes) + if err != nil { + log.Println("read remote client response error", err) + } + log.Println("remote client response: ", wsRes) + return wsRes +} + +func httpRequestToWebSocketRequest(r *http.Request) (ws WebSocketRequest) { + reqStr, err := captureRequestData(r) + if err != nil { + log.Println("captureRequestData error:", err) + } + log.Println("req serialized: ", reqStr) + + reqRemote := WebSocketRequest{Req: reqStr, URL: r.URL.String()} + return reqRemote +} +``` + +### Step3 After receiving the websocket message, the client forwards it to the localserver and returns the response of the localserver to the server + +```golang +go func() { + defer close(done) + for { + websocketReq := readWebSocketReq(c) + + localRequest := socketToLocalRequest(websocketReq, ggclient.ProxyLocalPort) + resp, err := (&http.Client{}).Do(localRequest) + if err != nil { + log.Println("local http request error:", err) + continue + } + + wsRes := localResponseToWebSocketResponse(resp) + + // log.Printf("client send response: %s \n", wsRes.Body) + c.WriteJSON(wsRes) + } + }() + +func socketToLocalRequest(websocketReq WebSocketRequest, port int) *http.Request { + r := bufio.NewReader(bytes.NewReader([]byte(websocketReq.Req))) + localRequest, err := http.ReadRequest(r) + if err != nil { + log.Println("deserialize request error", err) + return localRequest + } + + localRequest.RequestURI = "" + u, err := url.Parse(websocketReq.URL) + if err != nil { + log.Println("parse url error", err) + } + localRequest.URL = u + localRequest.URL.Scheme = "http" + localRequest.URL.Host = "localhost:" + strconv.Itoa(port) + return localRequest +} + +func localResponseToWebSocketResponse(resp *http.Response) WebSocketResponse { + body, err := io.ReadAll(resp.Body) + if err != nil { + log.Println("read local response error ", err) + } + resp.Body.Close() + wsRes := WebSocketResponse{Status: resp.Status, StatusCode: resp.StatusCode, + Proto: resp.Proto, Header: resp.Header, Body: body, ContentType: resp.Header.Get("Content-Type")} + return wsRes +} +``` + +### Step 4 After receiving the response, the server returns it to the http response + +```golang +func wsResToHttpResponse(w http.ResponseWriter, wsRes WebSocketResponse) { + copyHeader(w, wsRes) + io.Copy(w, bytes.NewReader(wsRes.Body)) +} +``` + +So far, though ggrok, we have implemented the local service proxy and published it on the Internet. The above are some main codes. See [GitHub](https://github.com/onyas/ggrok) for details. Please create issue or PR if you have any problems, and jointly create a more robust open source system. \ No newline at end of file