123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117openCCFuntypestate=(*| Connecting *)|Open|Closedletlowercase_headerheadersheader=List.assoc_optheaderheaders|>CCOpt.mapString.lowercase_asciiletheaderheadersheader=List.assoc_optheaderheadersletguid="258EAFA5-E914-47DA-95CA-C5AB0DC85B11"letis_websocket_upgradeheaders=letupgrade=lowercase_headerheaders"upgrade"inletconnection=lowercase_headerheaders"connection"inmatch(upgrade,connection)with|Some"websocket",Some"upgrade"->print_endline"matched websocket connection";true|_->falseletbuild_messageframes=List.fold_left(funmessagef->matchf.Frame.datawith|`Plains->s^message|`Masked(_,_)asdata->let`Plainunmasked_data=Frame.unmaskdatainunmasked_data^message)""framesmoduleMake(Io:Interface'.Io.S)=structmoduleM_result=Interface'.Monad_result.Make(Io.M)(structtypet=stringletof_exn=Printexc.to_stringend)openIo.MmoduleF=Frame.Make(Io)(M_result)letupgradeheaders=leterror_headers=[("sec-websocket-version","13")]inmatchis_websocket_upgradeheaderswith|true->letversion=lowercase_headerheaders"sec-websocket-version"inletkey=headerheaders"sec-websocket-key"inlethost=lowercase_headerheaders"host"in(* host header required but not used *)(match(version,host,key)with|Some"13",Some_,Somek->letaccept_key=k^guid|>Sha1.string|>Sha1.to_bin|>B64.encodeinOk[("upgrade","websocket");("connection","Upgrade");("sec-websocket-accept",accept_key)]|_->Errorerror_headers)|false->Errorerror_headerslethandle~is_serverhandlericoc=letstate=refOpeninletshould_mask=notis_serverinletwriter=function|None->letf=Frame.close~mask:should_mask1000instate:=Closed;F.write_frameocf|Somemsg->letframe=Frame.of_string~mask:should_maskTextmsginF.write_frameocframeinhandlerwriter>>=funhandle_in->letrecreaderbuffer=lethandle_data_frameframe=letbuffer'=frame::bufferinifframe.Frame.finthenletmessage=build_messagebuffer'inhandle_in(Somemessage)>>lazy(reader[])elsereaderbuffer'inF.read_frameic>>=(function|Error_->handle_inNone>>lazy(return())|Okframe->(matchframe.opcodewith|Text|Binary->(* text and binary handled the same, as sequence of bytes/chars *)ifList.lengthbuffer>0thenletf=Frame.close~mask:should_mask1002inF.write_frameocfelsehandle_data_frameframe|Continuation->ifList.lengthbuffer=0thenletf=Frame.close~mask:should_mask1002inF.write_frameocfelsehandle_data_frameframe|Ping->letsend_pongs=letf=Frame.of_string~mask:should_maskPongsinF.write_frameocfin(matchframe.datawith|`Masked_asdata->let`Plains=Frame.unmaskdatainsend_pongs|`Plains->send_pongs)|Close->(match!statewith|Open->letf=Frame.close~mask:should_mask1000inF.write_frameocf(* assuming closing in channel is enough... *)>>lazy(ifis_serverthenIo.close_inicelsereturn())>>lazy(return())|Closed->ifis_serverthenIo.close_inicelsereturn())|_->readerbuffer))inreader[]lethandle_serverhicoc=handle~is_server:truehicoclethandle_clienthicoc=handle~is_server:falsehicocend