:Class MildServer
:Field Public Config
:Field Public Port
:Field Public Root
:Field Public TempFolder
:Field Public Address
:Field Public DefaultPage
:Field Public DefaultExtension←'.dyalog'
:Field Public TrapErrors
:Field Public Debug
:Field Public UseContentEncoding←0
:Field Public RootCertDir←''
:Field Public CertFile←''
:Field Public KeyFile←''
:Field Public Secure←0
:Field Public SSLFlags←32+64 ⍝ Accept Without Validating, RequestClientCertificate
:Field Public IPVersion←'IPv4' ⍝ Default until further notice
:Field Public TID←¯1 ⍝ Thread ID Server is running under
:Field Public BlockSize←640000 ⍝ Blocksize when returning files
:Field Public SessionHandler←⎕NS ''
:Field Public Authentication←⎕NS ''
:field Public Encoders←⍬ ⍝ pointers to instances of content encoders
:field Public Datasources←⍬
⎕TRAP←0/⎕TRAP ⋄ ⎕ML ⎕IO←0 1
unicode←80=⎕DR 'A'
NL←(CR LF)←⎕UCS 13 10
FindFirst←{(⍺⍷⍵)⍳1}
fromutf8←{0::(⎕AV,'?')[⎕AVU⍳⍵] ⋄ 'UTF-8' ⎕UCS ⍵} ⍝ Turn raw UTF-8 input into text
toutf8←{'UTF-8' ⎕UCS ⍵} ⍝ Turn text into UTF-8 byte stream
⍝ ↓↓↓--- Methods which are usually overridden ---
∇ Wrap req
:Access Public Overridable
⍝ Add formatting to HTML if necessary
∇
∇ onSessionStart req
:Access Public Overridable
⍝ Process a new session
∇
∇ onSessionEnd session
:Access Public Overridable
⍝ Handle the end of a session
∇
∇ Error req
:Access Public Overridable
⍝ Handle trapped errors
req.Response.HTML←'',(⊃,/⎕DM,¨⊂'
'),''
req.Fail 500 ⍝ Internal Server Error
1 Log ⎕DM
∇
∇ level Log msg
:Access Public overridable
⍝ Logs server messages
⍝ levels implemented in MildServer are:
⍝ 1-error/important, 2-warning, 4-informational, 8-transaction (GET/POST)
⎕←msg ⍝ just output the message
∇
∇ Cleanup
:Access Public overridable
⍝ Perform any site specific cleanup
∇
⍝ ↑↑↑--- End of Overridable methods ---
⍝ ↓↓↓--- Begin MildServer Core Code
∇ r←Srv x
:If 3=⎕NC'#.DRC.Srv' ⋄ r←#.DRC.Srv x,⊂'Protocol'IPVersion ⍝ Conga v2.0+
:Else ⋄ r←#.DRC.Server(2≠⍳⍴x)/x ⋄ :EndIf ⍝ V1.0: Remove address
∇
∇ r←Clt x
:If 3=⎕NC'#.DRC.Clt' ⋄ r←#.DRC.Clt x,⊂'Protocol'IPVersion
:Else ⋄ r←#.DRC.Client x ⋄ :EndIf
∇
∇ Run
:Access Public
('Already Running on Thread',⍕TID)⎕SIGNAL(TID∊⎕TNUMS)/11
TID←RunServer&⍬
∇
∇ End;tempfiles
⍝ Called by destructor
:Access Public
:If 0=⎕NC'ServerName'
{}1 #.DRC.Init''
:Else
'Not running'⎕SIGNAL(#.DRC.Exists ServerName)↓11
{}#.DRC.Close ServerName
:Trap 6 ⍝ ⎕TSYNC may not return a value if the thread doesn't, so handle the possible VALUE ERROR
{}⎕TSYNC TID∩⎕TNUMS
:EndTrap
:EndIf
tempfiles←{(~⍵[;4])/⍵[;1]}#.Files.List TempFolder
:If 0≠⍴tempfiles←(tempfiles∨.≠¨'.')/tempfiles
{0::'' ⋄ #.Files.Delete TempFolder,⍵}¨tempfiles
:EndIf
Cleanup ⍝ overridable (see below)
TID←¯1
∇
∇ HandleMSP REQ;⎕TRAP;inst;class;z;file;props;lcp;args;i;ts;date;n;expired;data;m;oldinst;names;html;sessioned
⍝ Handle a "Mildserver Page" request
:If ~#.Files.Exists file←Root,1↓REQ.Page
REQ.Fail 404 ⍝ Not found
:Else
date←3⊃,(1↓REQ.Page)#.Files.List Root
:If sessioned←326=⎕DR REQ.Session ⍝ do we think we have a session handler active?
:AndIf 0≠⍴REQ.Session.Pages ⍝ Look for existing Page in Session
:AndIf (n←⍴REQ.Session.Pages)≥i←REQ.Session.Pages._PageName⍳⊂REQ.Page
inst←i⊃REQ.Session.Pages ⍝ Get existing instance
:If expired←inst._PageDate≢date ⍝ Timestamp unchanged?
oldinst←inst
REQ.Session.Pages~←inst
4 Log'Page: ',REQ.Page,' ... has been updated ...'
:EndIf
:AndIf ~expired
:Else ⍝ First use of Page in this Session, or page expired
:If 0≠⍴z←#.Files.GetText file
class←⎕SE.SALT.Load file,' -target=#.Pages'
:Trap 11
inst←⎕NEW class
:Else
REQ.Fail 500 ⋄ →0
:EndTrap
inst._PageName←REQ.Page
inst._PageDate←date
:If sessioned ⋄ REQ.Session.Pages,←inst ⋄ :EndIf
:If 0≠⎕NC'oldinst'
:AndIf (names←oldinst.⎕NL-2)≡inst.⎕NL-2 ⍝ Interface is indentical
:AndIf 0≠⍴names←('_'≠1⊃¨names)/names
4 Log' (interface is identical: data transferred)'
⍎'inst.(',names,')←oldinst.(',(names←⍕names),')' ⍝ Transfer values
:EndIf
:Else
REQ.Fail 404 ⋄ →0
:EndIf
:EndIf
⍝ Move arguments / parameters into Public Properties
:If 0≠1↑⍴data←{⍵[⍋↑⍵[;1];]}REQ.Arguments⍪REQ.Data
m←1,2≢/data[;1]
data←(m/data[;1]),[1.5]m⊂data[;2]
i←{⍵/⍳⍴⍵}1=⊃∘⍴¨data[;2]
data[i;2]←⊃¨data[i;2]
:EndIf
:If 0≠⍴lcp←props←('_'≠1⊃¨props)/props←(inst.⎕NL-2) ⍝ Get list of public properties (not lowercase)
:AndIf 0≠1↑⍴args←(data[;1]∊lcp)⌿data
args←(2⌈⍴args)⍴args
i←lcp⍳args[;1]
⍎'inst.(',(⍕props[i]),')←args[;2]'
:EndIf
:If (1=TrapErrors)∧9=⎕NC'#.DrA' ⋄ ⎕TRAP←#.DrA.TrapServer
:Else ⋄ ⎕TRAP←0 'S'
:EndIf
inst.Render REQ
:EndIf
→0
RESUME: ⍝ RESUME is used by DrA
⎕TRAP←0/⎕TRAP ⍝ Let framework trapping take over
REQ.Title'Unhandled Execution Error'
REQ.Style'styles/error.css'
html←'h1'#.HTMLInput.Enclose'Server Error in ''',REQ.Page,'''.