/* Ċ Ġ* ĠCopyright ĠÂ© Ġ 2 0 2 1 ĠPaul ĠAmb rose Ġ( p amb rose @ mac . com ) Ċ Ġ* Ċ Ġ* ĠLicensed Ġunder Ġthe ĠApache ĠLicense , ĠVersion Ġ 2 . 0 Ġ( the Ġ" License "); Ċ Ġ* Ġyou Ġmay Ġnot Ġuse Ġthis Ġfile Ġexcept Ġin Ġcompliance Ġwith Ġthe ĠLicense . Ċ Ġ* ĠYou Ġmay Ġobtain Ġa Ġcopy Ġof Ġthe ĠLicense Ġat Ċ Ġ* Ċ Ġ* ĠĠĠĠĠĠ Ġhttp :// www . apache . org / licenses / LICENSE - 2 . 0 Ċ Ġ* Ċ Ġ* ĠUnless Ġrequired Ġby Ġapplicable Ġlaw Ġor Ġagreed Ġto Ġin Ġwriting , Ġsoftware Ċ Ġ* Ġdistributed Ġunder Ġthe ĠLicense Ġis Ġdistributed Ġon Ġan Ġ" AS ĠIS " ĠBASIS , Ċ Ġ* ĠWITHOUT ĠWARRANTIES ĠOR ĠCONDITIONS ĠOF ĠANY ĠKIND , Ġeither Ġexpress Ġor Ġimplied . Ċ Ġ* ĠSee Ġthe ĠLicense Ġfor Ġthe Ġspecific Ġlanguage Ġgoverning Ġpermissions Ġand Ċ Ġ* Ġlimitations Ġunder Ġthe ĠLicense . Ċ Ġ* Ċ Ġ*/ Ċ Ċ package Ġcom . github . reading bat . server Ċ Ċ import Ġcom . github . p amb rose . common . util . isNotNull Ċ import Ġcom . github . p amb rose . common . util . maxLength Ċ import Ġcom . github . reading bat . common . Browser Session . Companion . find Session Db ms Id Ċ import Ġcom . github . reading bat . common . Constants . STATIC Ċ import Ġcom . github . reading bat . common . Constants . UNKNOWN _ USER _ ID Ċ import Ġcom . github . reading bat . common . Endpoints . PING _ ENDPOINT Ċ import Ġcom . github . reading bat . common . User . Companion . fetch User Db ms Id From Cache Ċ import Ġcom . github . reading bat . common . browser Session Ċ import Ġcom . github . reading bat . dsl . is Save Requests Enabled Ċ import Ġcom . github . reading bat . server . Geo Info . Companion . lookup Geo Info Ċ import Ġcom . github . reading bat . server . Geo Info . Companion . query Geo Info Ċ import Ġcom . github . reading bat . server . Intercept s . clock Ċ import Ġcom . github . reading bat . server . Intercept s . logger Ċ import Ġcom . github . reading bat . server . Intercept s . request Timing Map Ċ import Ġcom . github . reading bat . server . Server Utils . fetch User Db ms Id From Cache Ċ import Ġio . ktor . application .* Ċ import Ġio . ktor . features .* Ċ import Ġio . ktor . http . HttpHeaders . UserAgent Ċ import Ġio . ktor . request .* Ċ import Ġio . ktor . routing . Routing . Feature . Routing Call Finished Ċ import Ġio . ktor . routing . Routing . Feature . Routing Call Started Ċ import Ġmu . K Logging Ċ import Ġorg . jetbrains . ex posed . sql . insert Ċ import Ġorg . jetbrains . ex posed . sql . transactions . transaction Ċ import Ġorg . jetbrains . ex posed . sql . update Ċ import Ġjava . util . concurrent . Concurrent HashMap Ċ import Ġkotlin . concurrent . timer Ċ import Ġkotlin . time . Duration . Companion . hours Ċ import Ġkotlin . time . Duration . Companion . minutes Ċ import Ġkotlin . time . Time Mark Ċ import Ġkotlin . time . Time Source Ċ Ċ internal Ġobject ĠInter cept s Ġ: ĠK Logging () Ġ{ ĊĠ Ġval Ġclock Ġ= ĠTime Source . Mon otonic ĊĠ Ġval Ġrequest Timing Map Ġ= ĠConcurrent HashMap < String , ĠTime Mark >() ĊĊĠ Ġ@ Suppress (" unused ") ĊĠ Ġval Ġtimer Ġ= ĊĠĠĠ Ġtimer (" request Timing Map Ġadmin ", Ġfalse , Ġminutes ( 1 ). in Whole Milliseconds , Ġminutes ( 1 ). in Whole Milliseconds ) Ġ{ ĊĠĠĠĠĠ Ġrequest Timing Map ĊĠĠĠĠĠĠĠ Ġ. filter Ġ{ Ġ(_, Ġstart ) Ġ-> Ġstart . elapsed Now () Ġ> Ġhours ( 1 ) Ġ} ĊĠĠĠĠĠĠĠ Ġ. forEach Ġ{ Ġ( call Id , Ġstart ) Ġ-> ĊĠĠĠĠĠĠĠĠĠ Ġrequest Timing Map . remove ( call Id ) ĊĠĠĠĠĠĠĠĠĠĠĠ Ġ? . also Ġ{ ĊĠĠĠĠĠĠĠĠĠĠĠĠĠ Ġlogger . info Ġ{ Ġ" Removing Ġrequest Timing Map : Ġ$ call Id Ġafter Ġ${ start . elapsed Now () }" Ġ} ĊĠĠĠĠĠĠĠĠĠĠĠĠĠ Ġupdate Server Request ( call Id , Ġstart ) ĊĠĠĠĠĠĠĠĠĠĠĠ Ġ} ĊĠĠĠĠĠĠĠĠĠĠĠ Ġ?: Ġlogger . info Ġ{ Ġ" Unable Ġto Ġremove Ġrequest Timing Map Ġitem : Ġ$ call Id Ġ${ start . elapsed Now () }" Ġ} ĊĠĠĠĠĠĠĠ Ġ} ĊĠĠĠ Ġ} Ċ } Ċ Ċ internal Ġfun ĠApplication . intercept s () Ġ{ ĊĊĠ Ġintercept ( Application Call Pipeline . Setup ) Ġ{ ĊĠĠĠ Ġ// ĠPhase Ġfor Ġpre paring Ġcall Ġand Ġit 's Ġattributes Ġfor Ġprocessing ĊĠ Ġ} ĊĊĠ Ġintercept ( Application Call Pipeline . Monitoring ) Ġ{ ĊĠĠĠ Ġ// ĠPhase Ġfor Ġtracing Ġcalls , Ġuseful Ġfor Ġlogging , Ġmetrics , Ġerror Ġhandling Ġand Ġso Ġon ĊĠ Ġ} ĊĊĠ Ġintercept ( Application Call Pipeline . Features ) Ġ{ ĊĠĠĠ Ġ// ĠPhase Ġfor Ġfeatures . ĠMost Ġfeatures Ġshould Ġintercept Ġthis Ġphase ĊĠĠĠ Ġif Ġ(! is Static Call ()) ĊĠĠĠĠĠ Ġtry Ġ{ ĊĠĠĠĠĠĠĠ Ġval Ġbrowser Session Ġ= Ġcall . browser Session ĊĠĠĠĠĠĠĠ Ġif Ġ( is Save Requests Enabled () Ġ&& Ġbrowser Session . isNotNull ()) Ġ{ ĊĠĠĠĠĠĠĠĠĠ Ġval Ġrequest Ġ= Ġcall . request ĊĠĠĠĠĠĠĠĠĠ Ġval Ġip Address Ġ= Ġrequest . origin . remote Host ĊĠĠĠĠĠĠĠĠĠ Ġval Ġsession Db ms Id Ġ= Ġtransaction Ġ{ Ġfind Session Db ms Id ( browser Session . id , Ġtrue ) Ġ} ĊĠĠĠĠĠĠĠĠĠ Ġval Ġuser Db ms Id Ġ= ĊĠĠĠĠĠĠĠĠĠĠĠ Ġcall . fetch User Db ms Id From Cache (). take If Ġ{ Ġit Ġ!= Ġ- 1 L Ġ} Ġ?: Ġfetch User Db ms Id From Cache ( UNKNOWN _ USER _ ID ) ĊĠĠĠĠĠĠĠĠĠ Ġval Ġverb Ġ= Ġrequest . http Method . value ĊĠĠĠĠĠĠĠĠĠ Ġval Ġpath Ġ= Ġrequest . path () ĊĠĠĠĠĠĠĠĠĠ Ġval ĠqueryString Ġ= Ġrequest . queryString () ĊĠĠĠĠĠĠĠĠĠ Ġ// ĠUse Ġhttps :// ip geo location . io / documentation / user - agent - api . html Ġto Ġparse Ġuser Agent Ġdata ĊĠĠĠĠĠĠĠĠĠ Ġval Ġuser Agent Ġ= Ġrequest . headers [ UserAgent ] Ġ?: Ġ"" ĊĊĠĠĠĠĠĠĠĠĠ Ġval Ġgeo Info Ġ= Ġlookup Geo Info ( ipAddress ) ĊĠĠĠĠĠĠĠĠĠ Ġval Ġgeo Db ms Id Ġ= ĊĠĠĠĠĠĠĠĠĠĠĠ Ġif Ġ( geo Info . require Db ms Look Up ) ĊĠĠĠĠĠĠĠĠĠĠĠĠĠ Ġquery Geo Info ( ipAddress )?. dbms Id Ġ?: Ġerror (" Missing Ġip Ġaddress : Ġ$ ipAddress ") ĊĠĠĠĠĠĠĠĠĠĠĠ Ġelse ĊĠĠĠĠĠĠĠĠĠĠĠĠĠ Ġgeo Info . dbms Id ĊĊĠĠĠĠĠĠĠĠĠ Ġlogger . debug Ġ{ Ġ" Saving Ġrequest : Ġ${ call . call Id } Ġ$ ipAddress Ġ$ user Db ms Id Ġ$ verb Ġ$ path Ġ$ queryString Ġ$ geo Db ms Id " Ġ} ĊĠĠĠĠĠĠĠĠĠ Ġtransaction Ġ{ ĊĠĠĠĠĠĠĠĠĠĠĠ ĠServer Requests Table ĊĠĠĠĠĠĠĠĠĠĠĠĠĠ Ġ. insert Ġ{ Ġrow Ġ-> ĊĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ Ġrow [ requestId ] Ġ= Ġcall . call Id Ġ?: Ġ" None " ĊĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ Ġrow [ session Ref ] Ġ= Ġsession Db ms Id ĊĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ Ġrow [ user Ref ] Ġ= Ġuser Db ms Id ĊĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ Ġrow [ geo Ref ] Ġ= Ġgeo Db ms Id ĊĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ Ġrow [ Server Requests Table . verb ] Ġ= Ġverb ĊĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ Ġrow [ Server Requests Table . path ] Ġ= Ġpath . maxLength ( 2 5 6 ) ĊĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ Ġrow [ Server Requests Table . queryString ] Ġ= ĠqueryString . maxLength ( 2 5 6 ) ĊĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ Ġrow [ Server Requests Table . userAgent ] Ġ= Ġuser Agent . maxLength ( 2 5 6 ) ĊĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ Ġrow [ duration ] Ġ= Ġ 0 ĊĠĠĠĠĠĠĠĠĠĠĠĠĠ Ġ} ĊĠĠĠĠĠĠĠĠĠ Ġ} ĊĠĠĠĠĠĠĠ Ġ} ĊĠĠĠĠĠ Ġ} Ġcatch Ġ( e : ĠThrowable ) Ġ{ Ċ // ĠĠĠĠĠĠĠ Ġlogger . warn ( e ) Ġ{} ĊĠĠĠĠĠĠĠ Ġlogger . info Ġ{ Ġ" Failure Ġsaving Ġrequest : Ġ${ e . message }" Ġ} ĊĠĠĠĠĠ Ġ} ĊĠ Ġ} ĊĊĠ Ġintercept ( Application Call Pipeline . Call ) Ġ{ ĊĠĠĠ Ġ// ĠPhase Ġfor Ġprocessing Ġa Ġcall Ġand Ġsending Ġa Ġresponse ĊĠ Ġ} ĊĊĠ Ġintercept ( Application Call Pipeline . Fallback ) Ġ{ ĊĠĠĠ Ġ// ĠPhase Ġfor Ġhandling Ġun processed Ġcalls ĊĠ Ġ} ĊĊĠ Ġif Ġ( is Save Requests Enabled ()) Ġ{ ĊĠĠĠ Ġenvironment . monitor ĊĠĠĠĠĠ Ġ. apply Ġ{ ĊĠĠĠĠĠĠĠ Ġsubscribe ( Routing Call Started ) Ġ{ Ġcall Ġ-> ĊĠĠĠĠĠĠĠĠĠ Ġval Ġpath Ġ= Ġcall . request . path () ĊĠĠĠĠĠĠĠĠĠ Ġif Ġ(! path . startsWith ("/ $ STATIC /") Ġ&& Ġpath Ġ!= ĠP ING _ ENDPOINT ) Ġ{ ĊĠĠĠĠĠĠĠĠĠĠĠ Ġcall . call Id ĊĠĠĠĠĠĠĠĠĠĠĠĠĠ Ġ. also Ġ{ Ġcall Id Ġ-> ĊĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ Ġif Ġ( call Id . isNotNull ()) Ġ{ ĊĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ Ġrequest Timing Map [ call Id ] Ġ= Ġclock . mark Now () ĊĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ Ġ} ĊĠĠĠĠĠĠĠĠĠĠĠĠĠ Ġ} ĊĠĠĠĠĠĠĠĠĠ Ġ} ĊĠĠĠĠĠĠĠ Ġ} ĊĊĠĠĠĠĠĠĠ Ġsubscribe ( Routing Call Finished ) Ġ{ Ġcall Ġ-> ĊĠĠĠĠĠĠĠĠĠ Ġval Ġpath Ġ= Ġcall . request . path () ĊĠĠĠĠĠĠĠĠĠ Ġif Ġ(! path . startsWith ("/ $ STATIC /") Ġ&& Ġpath Ġ!= ĠP ING _ ENDPOINT ) Ġ{ ĊĠĠĠĠĠĠĠĠĠĠĠ Ġcall . call Id ĊĠĠĠĠĠĠĠĠĠĠĠĠĠ Ġ. also Ġ{ Ġcall Id Ġ-> ĊĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ Ġif Ġ( call Id . isNotNull ()) Ġ{ ĊĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ Ġrequest Timing Map . remove ( call Id ) ĊĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ Ġ. also Ġ{ Ġstart Ġ-> ĊĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ Ġif Ġ( start . isNotNull ()) Ġ{ ĊĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ Ġlogger . debug Ġ{ Ġ" Logged Ġcall Ġ${ request Timing Map . size } Ġ${ start . elapsed Now ()} Ġ${ call . call Id } Ġ${ call . request . to Log String () }" Ġ} ĊĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ Ġupdate Server Request ( call Id , Ġstart ) ĊĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ Ġ} ĊĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ Ġ} ĊĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ Ġ} Ġelse Ġ{ ĊĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ Ġlogger . error Ġ{ Ġ" Null ĠrequestId Ġfor Ġ$ path " Ġ} ĊĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ Ġ} ĊĠĠĠĠĠĠĠĠĠĠĠĠĠ Ġ} ĊĠĠĠĠĠĠĠĠĠ Ġ} ĊĠĠĠĠĠĠĠ Ġ} ĊĠĠĠĠĠ Ġ} ĊĠ Ġ} Ċ } Ċ Ċ fun Ġupdate Server Request ( call Id : ĠString , Ġstart : ĠTime Mark ) Ġ{ ĊĠ Ġtransaction Ġ{ ĊĠĠĠ ĠServer Requests Table ĊĠĠĠĠĠ Ġ. update ({ ĠServer Requests Table . requestId Ġeq Ġcall Id Ġ}) Ġ{ Ġrow Ġ-> ĊĠĠĠĠĠĠĠ Ġrow [ duration ] Ġ= Ġstart . elapsed Now (). in Whole Milliseconds ĊĠĠĠĠĠ Ġ} ĊĠ Ġ} Ċ } Ċ Ċ fun ĠPipeline Call . is Static Call () Ġ= Ġcontext . request . path (). startsWith ("/ $ STATIC /") Ċ