123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188openBaseopenAsync_kernelopenAsync_unixopenAsync_extramoduleRequest=structincludeCohttp.Requestinclude(Make(Io):moduletypeofMake(Io)withtypet:=t)endmoduleResponse=structincludeCohttp.Responseinclude(Make(Io):moduletypeofMake(Io)withtypet:=t)endtype('address,'listening_on)t={server:('address,'listening_on)Tcp.Server.tsexp_opaque;}[@@derivingsexp_of]typeresponse=Response.t*Body.t[@@derivingsexp_of]typeresponse_action=[`ExpertofCohttp.Response.t*(Io.ic->Io.oc->unitDeferred.t)|`Responseofresponse]type'rrespond_t=?flush:bool->?headers:Cohttp.Header.t->?body:Body.t->Cohttp.Code.status_code->'rDeferred.tletcloset=Tcp.Server.closet.serverletclose_finishedt=Tcp.Server.close_finishedt.serverletis_closedt=Tcp.Server.is_closedt.serverletlistening_ont=Tcp.Server.listening_ont.serverletread_bodyreqrd=matchRequest.has_bodyreqwith(* TODO maybe attempt to read body *)|`No|`Unknown->(`Empty,Deferred.unit)|`Yes->(* Create a Pipe for the body *)letreader=Request.make_body_readerreqrdinletpipe=Body_raw.pipe_of_bodyRequest.read_body_chunkreaderin(`Pipepipe,Pipe.closedpipe)letcollect_errorswriter~f=letmonitor=Writer.monitorwriterin(* don't propagate errors up, we handle them here *)Monitor.detach_and_get_error_streammonitor|>(ignore:exnStream.t->unit);choose[choice(Monitor.get_next_errormonitor)(fune->Error(Exn.Reraised("Cohttp_async.Server.collect_errors",e)));choice(try_with~name:"Cohttp_async.Server.collect_errors"f)Fn.id;];;lethandle_clienthandle_requestsockrdwr=collect_errorswr~f:(fun()->letlast_body_pipe_drained=refDeferred.unitinletrequests_pipe=Reader.read_allrd(funrd->!last_body_pipe_drained>>=fun()->(* [`Expert] responses may close the [Reader.t] *)ifReader.is_closedrdthenreturn`EofelsebeginRequest.readrd>>=function|`Eof|`Invalid_->return`Eof|`Okreq->letbody,finished=read_bodyreqrdinhandle_request~bodysockreq>>|function|`Expert(headers,io_handler)->letexpert_finished=Ivar.create()inlast_body_pipe_drained:=Deferred.all_unit[Ivar.readexpert_finished;finished];`Ok(`Expert(headers,io_handler,body,expert_finished))|`Responser->last_body_pipe_drained:=finished;`Ok(`Response(req,body,r))end)inPipe.iter~continue_on_error:falserequests_pipe~f:(function|`Expert(response,io_handler,body,finished)->Response.write_headerresponsewr>>=fun()->io_handlerrdwr>>=fun()->Body.drainbody>>|fun()->Ivar.fill_if_emptyfinished()|`Response(req,body,(res,res_body))->letkeep_alive=Request.is_keep_alivereqinletflush=Response.flushresinletres=letheaders=Cohttp.Header.add_unless_exists(Cohttp.Response.headersres)"connection"(ifkeep_alivethen"keep-alive"else"close")in{reswithResponse.headers}inResponse.write~flush(Body_raw.write_bodyResponse.write_bodyres_body)reswr>>=fun()->Writer.(ifkeep_alivethenflushedelseclose?force_close:None)wr>>=fun()->Body.drainbody))>>=funres->Writer.closewr>>=fun()->Reader.closerd>>|fun()->Result.ok_exnresletrespond?(flush=true)?(headers=Cohttp.Header.init())?(body=`Empty)status:responseDeferred.t=letencoding=Body.transfer_encodingbodyinletresp=Response.make~status~flush~encoding~headers()inreturn(resp,body)letrespond_with_pipe?flush?headers?(code=`OK)body=respond?flush?headers~body:(`Pipebody)codeletrespond_string?flush?headers?(status=`OK)body=respond?flush?headers~body:(`Stringbody)statusletrespond_with_redirect?headersuri=letheaders=Cohttp.Header.add_opt_unless_existsheaders"location"(Uri.to_stringuri)inrespond~flush:false~headers`Foundletresolve_local_file~docroot~uri=(* This normalises the Uri and strips out .. characters *)Uri.(pct_decode(path(resolve""(of_string"/")uri)))|>Caml.Filename.concatdocrootleterror_body_default="<html><body><h1>404 Not Found</h1></body></html>"letrespond_with_file?flush?headers?(error_body=error_body_default)filename=Monitor.try_with~run:`Now(fun()->Reader.open_filefilename>>=funrd->letbody=`Pipe(Reader.piperd)inletmime_type=Magic_mime.lookupfilenameinletheaders=Cohttp.Header.add_opt_unless_existsheaders"content-type"mime_typeinrespond?flush~headers~body`OK)>>=function|Okres->returnres|Error_exn->respond_string~status:`Not_founderror_bodytypemode=Conduit_async.serverletcreate_raw?max_connections?backlog?buffer_age_limit?(mode=`TCP)~on_handler_errorwhere_to_listenhandle_request=Conduit_async.serve?max_connections?backlog?buffer_age_limit~on_handler_errormodewhere_to_listen(handle_clienthandle_request)>>|funserver->{server}letcreate_expert?max_connections?backlog?buffer_age_limit?(mode=`TCP)~on_handler_errorwhere_to_listenhandle_request=create_raw?max_connections?backlog?buffer_age_limit~on_handler_error~modewhere_to_listenhandle_requestletcreate?max_connections?backlog?buffer_age_limit?(mode=`TCP)~on_handler_errorwhere_to_listenhandle_request=lethandle_request~bodyaddressrequest=handle_request~bodyaddressrequest>>|funr->`Responserincreate_raw?max_connections?backlog?buffer_age_limit~on_handler_error~modewhere_to_listenhandle_request