123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484(*****************************************************************************)(* *)(* Open Source License *)(* Copyright (c) 2018 Dynamic Ledger Solutions, Inc. <contact@tezos.com> *)(* Copyright (c) 2020-2021 Nomadic Labs <contact@nomadic-labs.com> *)(* *)(* Permission is hereby granted, free of charge, to any person obtaining a *)(* copy of this software and associated documentation files (the "Software"),*)(* to deal in the Software without restriction, including without limitation *)(* the rights to use, copy, modify, merge, publish, distribute, sublicense, *)(* and/or sell copies of the Software, and to permit persons to whom the *)(* Software is furnished to do so, subject to the following conditions: *)(* *)(* The above copyright notice and this permission notice shall be included *)(* in all copies or substantial portions of the Software. *)(* *)(* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR*)(* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *)(* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL *)(* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER*)(* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING *)(* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER *)(* DEALINGS IN THE SOFTWARE. *)(* *)(*****************************************************************************)openLwt.InfixmoduletypeCALL=sigvalcall:?headers:Cohttp.Header.t->?body:Cohttp_lwt.Body.t->Cohttp.Code.meth->Uri.t->(Cohttp.Response.t*Cohttp_lwt.Body.t)Lwt.tendmoduleOfCohttp(Client:Cohttp_lwt.S.Client):CALL=structletcall?headers?bodymethuri=Client.call?headers?bodymethuriendmoduleMake(Encoding:Resto.ENCODING)(Call:CALL)=structopenCohttpmoduleMedia_type=Media_type.Make(Encoding)moduleService=Resto.MakeService(Encoding)typecontent_type=string*stringtyperaw_content=Cohttp_lwt.Body.t*content_typeoptiontypecontent=Cohttp_lwt.Body.t*content_typeoption*Media_type.toptiontype('o,'e)generic_rest_result=[`Okof'ooption|`Conflictof'e|`Errorof'e|`Forbiddenof'e|`Not_foundof'e|`Goneof'e|`Unauthorizedof'e|`Bad_requestofstring|`Method_not_allowedofstringlist|`Unsupported_media_type|`Not_acceptableofstring|`Unexpected_status_codeofCohttp.Code.status_code*content|`Connection_failedofstring|`OCaml_exceptionofstring|`Unauthorized_hostofstringoption]type('o,'e)service_result=[('o,'eoption)generic_rest_result|`Unexpected_content_typeofraw_content|`Unexpected_contentof(string*Media_type.t)*string|`Unexpected_error_content_typeofraw_content|`Unexpected_error_contentof(string*Media_type.t)*string]moduletypeLOGGER=sigtyperequestvallog_empty_request:Uri.t->requestLwt.tvallog_request:?media:Media_type.t->'aEncoding.t->Uri.t->string->requestLwt.tvallog_response:request->?media:Media_type.t->'aEncoding.t->Cohttp.Code.status_code->stringLwt.tLazy.t->unitLwt.tendtypelogger=(moduleLOGGER)letnull_logger=(modulestructtyperequest=unitletlog_empty_request_=Lwt.return_unitletlog_request?media:____=Lwt.return_unitletlog_response_?media:____=Lwt.return_unitend:LOGGER)lettimings_logger~gettimeofdayppf=(modulestructtyperequest=string*floatletlog_empty_requesturi=lettzero=gettimeofday()inLwt.return(Uri.to_stringuri,tzero)letlog_request?media:__encuri_body=log_empty_requesturiletlog_response(uri,tzero)?media:__enc_code_body=lettime=gettimeofday()-.tzeroinFormat.fprintfppf"Request to %s succeeded in %gs@."uritime;Lwt.return_unitend:LOGGER)letfaked_media={Media_type.name=AnyMedia;q=None;pp=(fun_encppfs->Format.fprintfppf"@[<h 0>%a@]"Format.pp_print_texts);construct=(fun_->assertfalse);construct_seq=(fun_->assertfalse);destruct=(fun_->assertfalse);}letfull_loggerppf=(modulestructletcpt=ref0typerequest=int*stringletlog_empty_requesturi=letid=!cptinleturi=Uri.to_stringuriinincrcpt;Format.fprintfppf">>>>%d: %s@."iduri;Lwt.return(id,uri)letlog_request?(media=faked_media)encuribody=letid=!cptinleturi=Uri.to_stringuriinincrcpt;Format.fprintfppf"@[<v 2>>>>>%d: %s@,%a@]@."iduri(media.ppenc)body;Lwt.return(id,uri)letlog_response(id,_uri)?(media=faked_media)enccodebody=Lazy.forcebody>>=funbody->Format.fprintfppf"@[<v 2><<<<%d: %s@,%a@]@."id(Cohttp.Code.string_of_statuscode)(media.ppenc)body;Lwt.return_unitend:LOGGER)letfind_mediareceivedmedia_types=matchreceivedwith|Somereceived->Media_type.find_mediareceivedmedia_types|None->(matchmedia_typeswith[]->None|m::_->Somem)typelog={log:'a.?media:Media_type.t->'aEncoding.t->Cohttp.Code.status_code->stringLwt.tLazy.t->unitLwt.t;}letgeneric_callmeth?(headers=[])?accept?body?mediauri:(content,content)generic_rest_resultLwt.t=lethost=match(Uri.hosturi,Uri.porturi)with|(None,_)->None|(Somehost,None)->Somehost|(Somehost,Someport)->Some(host^":"^string_of_intport)inletinit_headers=matchhostwith|None->Header.init()|Somehost->Header.replace(Header.init())"host"hostinletheaders=List.fold_left(funheaders(header,value)->letheader=String.lowercase_asciiheaderinifheader<>"host"&&(String.lengthheader<2||String.subheader02<>"x-")theninvalid_arg"Resto_cohttp.Client.call: only headers \"host\" or starting \
with \"x-\" are supported"elseHeader.replaceheadersheadervalue)init_headersheadersinlet(body,headers)=match(body,media)with|(None,_)->(Cohttp_lwt.Body.empty,headers)|(Somebody,None)->(body,headers)|(Somebody,Somemedia)->(body,Header.addheaders"content-type"(Media_type.namemedia))inletheaders=matchacceptwith|None->headers|Someranges->Header.addheaders"accept"(Media_type.accept_headerranges)inLwt.catch(fun()->Call.call~headers(meth:>Code.meth)~bodyuri>>=fun(response,ansbody)->letheaders=Response.headersresponseinletmedia_name=matchHeader.get_media_typeheaderswith|None->None|Somes->(matchResto.Utils.split_pathswith|[x;y]->Some(x,y)|_->None)(* ignored invalid *)inletmedia=matchacceptwith|None->None|Somemedia_types->find_mediamedia_namemedia_typesinletstatus=Response.statusresponseinmatchstatuswith|`OK->Lwt.return(`Ok(Some(ansbody,media_name,media)))|`No_content->Lwt.return(`OkNone)|`Created->(* TODO handle redirection ?? *)failwith"Resto_cohttp_client.generic_json_call: unimplemented"|`Unauthorized->Lwt.return(`Unauthorized(ansbody,media_name,media))|`ForbiddenwhenCohttp.Header.memheaders"X-OCaml-Resto-CORS-Error"->Lwt.return(`Unauthorized_hosthost)|`Forbidden->Lwt.return(`Forbidden(ansbody,media_name,media))|`Not_found->Lwt.return(`Not_found(ansbody,media_name,media))|`Gone->Lwt.return(`Gone(ansbody,media_name,media))|`Conflict->Lwt.return(`Conflict(ansbody,media_name,media))|`Internal_server_error->ifmedia_name=Some("text","ocaml.exception")thenCohttp_lwt.Body.to_stringansbody>>=funmsg->Lwt.return(`OCaml_exceptionmsg)elseLwt.return(`Error(ansbody,media_name,media))|`Bad_request->Cohttp_lwt.Body.to_stringansbody>>=funbody->Lwt.return(`Bad_requestbody)|`Method_not_allowed->letallowed=Cohttp.Header.get_multiheaders"accept"inLwt.return(`Method_not_allowedallowed)|`Unsupported_media_type->Lwt.return`Unsupported_media_type|`Not_acceptable->Cohttp_lwt.Body.to_stringansbody>>=funbody->Lwt.return(`Not_acceptablebody)|code->Lwt.return(`Unexpected_status_code(code,(ansbody,media_name,media))))(funexn->letmsg=matchexnwith|Failuremsg->msg|Invalid_argumentmsg->msg|e->Printexc.to_stringeinLwt.return(`Connection_failedmsg))lethandle_errorlogservice(body,media_name,media)statusf=Cohttp_lwt.Body.is_emptybody>>=funempty->ifemptythenlog.logEncoding.untypedstatus(lazy(Lwt.return""))>>=fun()->Lwt.return(fNone)elsematchmediawith|None->Lwt.return(`Unexpected_error_content_type(body,media_name))|Somemedia->(Cohttp_lwt.Body.to_stringbody>>=funbody->leterror=Service.error_encodingserviceinlog.log~mediaerrorstatus(lazy(Lwt.returnbody))>>=fun()->matchmedia.Media_type.destructerrorbodywith|Okbody->Lwt.return(f(Somebody))|Errormsg->Lwt.return(`Unexpected_error_content((body,media),msg)))letprepare(typei)media_types?(logger=null_logger)?base(service:(_,_,_,_,i,_,_)Service.t)paramsquerybody=letmoduleLogger=(vallogger:LOGGER)inletmedia=matchMedia_type.first_complete_mediamedia_typeswith|None->invalid_arg"Resto_cohttp_client.call_service"|Some(_,m)->minlet{Service.meth;uri;input}=Service.forge_request?baseserviceparamsqueryin(matchinputwith|Service.No_input->Logger.log_empty_requesturi>>=funlog_request->Lwt.return(None,None,log_request)|Service.Inputinput->letbody=media.Media_type.constructinputbodyinLogger.log_request~mediainputuribody>>=funlog_request->Lwt.return(Some(Cohttp_lwt.Body.of_stringbody),Somemedia,log_request))>>=fun(body,media,log_request)->letlog={log=(fun?media->Logger.log_responselog_request?media)}inLwt.return(log,meth,uri,body,media)letcall_servicemedia_types?logger?headers?baseserviceparamsquerybody=preparemedia_types?logger?baseserviceparamsquerybody>>=fun(log,meth,uri,body,media)->generic_callmeth?headers~accept:media_types?body?mediauri>>=(function|`OkNone->log.logEncoding.untyped`No_content(lazy(Lwt.return""))>>=fun()->Lwt.return(`OkNone)|`Ok(Some(body,media_name,media))->(matchmediawith|None->Lwt.return(`Unexpected_content_type(body,media_name))|Somemedia->(Cohttp_lwt.Body.to_stringbody>>=funbody->letoutput=Service.output_encodingserviceinlog.log~mediaoutput`OK(lazy(Lwt.returnbody))>>=fun()->matchmedia.destructoutputbodywith|Okbody->Lwt.return(`Ok(Somebody))|Errormsg->Lwt.return(`Unexpected_content((body,media),msg))))|`Conflictbody->handle_errorlogservicebody`Conflict(funv->`Conflictv)|`Errorbody->handle_errorlogservicebody`Internal_server_error(funv->`Errorv)|`Forbiddenbody->handle_errorlogservicebody`Forbidden(funv->`Forbiddenv)|`Gonebody->handle_errorlogservicebody`Gone(funv->`Gonev)|`Not_foundbody->handle_errorlogservicebody`Not_found(funv->`Not_foundv)|`Unauthorizedbody->handle_errorlogservicebody`Unauthorized(funv->`Unauthorizedv)|(`Bad_request_|`Method_not_allowed_|`Unsupported_media_type|`Not_acceptable_|`Unexpected_status_code_|`Connection_failed_|`OCaml_exception_|`Unauthorized_host_)aserr->Lwt.returnerr)>>=funans->Lwt.return(meth,uri,ans)letcall_streamed_servicemedia_types?logger?headers?baseservice~on_chunk~on_closeparamsquerybody=preparemedia_types?logger?baseserviceparamsquerybody>>=fun(log,meth,uri,body,media)->generic_callmeth?headers~accept:media_types?body?mediauri>>=(function|`OkNone->on_close();log.logEncoding.untyped`No_content(lazy(Lwt.return""))>>=fun()->Lwt.return(`OkNone)|`Ok(Some(body,media_name,media))->(matchmediawith|None->Lwt.return(`Unexpected_content_type(body,media_name))|Somemedia->(letstream=Cohttp_lwt.Body.to_streambodyinLwt_stream.getstream>>=function|None->on_close();Lwt.return(`OkNone)|Somechunk->letbuffer=Buffer.create2048inletoutput=Service.output_encodingserviceinletrecloop=function|None->on_close();Lwt.return_unit|Somechunk->(Buffer.add_stringbufferchunk;letdata=Buffer.contentsbufferinlog.log~mediaoutput`OK(lazy(Lwt.returnchunk))>>=fun()->matchmedia.destructoutputdatawith|Okbody->Buffer.resetbuffer;on_chunkbody;Lwt_stream.getstream>>=loop|Error_msg->Lwt_stream.getstream>>=loop)inignore(loop(Somechunk):unitLwt.t);Lwt.return(`Ok(Some(fun()->ignore(Lwt_stream.junk_while(fun_->true)stream:unitLwt.t);())))))|`Conflictbody->handle_errorlogservicebody`Conflict(funv->`Conflictv)|`Errorbody->handle_errorlogservicebody`Internal_server_error(funv->`Errorv)|`Forbiddenbody->handle_errorlogservicebody`Forbidden(funv->`Forbiddenv)|`Gonebody->handle_errorlogservicebody`Gone(funv->`Gonev)|`Not_foundbody->handle_errorlogservicebody`Not_found(funv->`Not_foundv)|`Unauthorizedbody->handle_errorlogservicebody`Unauthorized(funv->`Unauthorizedv)|(`Bad_request_|`Method_not_allowed_|`Unsupported_media_type|`Not_acceptable_|`Unexpected_status_code_|`Connection_failed_|`OCaml_exception_|`Unauthorized_host_)aserr->Lwt.returnerr)>>=funans->Lwt.return(meth,uri,ans)end