mtx-server.lua /size: 16 Kb    last modification: 2020-07-01 14:35
1
if
not
modules
then
modules
=
{
}
end
modules
[
'
mtx-server
'
]
=
{
2
version
=
1
.
001
,
3
comment
=
"
companion to mtxrun.lua
"
,
4
author
=
"
Hans Hagen & Taco Hoekwater
"
,
5
copyright
=
"
PRAGMA ADE / ConTeXt Development Team
"
,
6
license
=
"
see context related readme files
"
7
}
8 9
local
helpinfo
=
[[
10<?xml version="1.0"?> 11<application> 12 <metadata> 13 <entry name="name">mtx-server</entry> 14 <entry name="detail">Simple Webserver For Helpers</entry> 15 <entry name="version">0.10</entry> 16 </metadata> 17 <flags> 18 <category name="basic"> 19 <subcategory> 20 <flag name="start"><short>start server</short></flag> 21 <flag name="port"><short>port to listen to</short></flag> 22 <flag name="root"><short>server root</short></flag> 23 <flag name="scripts"><short>scripts sub path</short></flag> 24 <flag name="index"><short>index file</short></flag> 25 <flag name="auto"><short>start on own path</short></flag> 26 </subcategory> 27 </category> 28 </flags> 29</application> 30
]]
31 32
local
application
=
logs
.
application
{
33
name
=
"
mtx-server
"
,
34
banner
=
"
Simple Webserver For Helpers 0.10
"
,
35
helpinfo
=
helpinfo
,
36
}
37 38
local
tonumber
,
tostring
,
loadfile
,
type
=
tonumber
,
tostring
,
loadfile
,
type
39
local
find
,
gsub
=
string
.
find
,
string
.
gsub
40
local
joinpath
,
filesuffix
,
dirname
,
is_qualified_path
=
file
.
join
,
file
.
suffix
,
file
.
dirname
,
file
.
is_qualified_path
41
local
loaddata
=
io
.
loaddata
42
local
P
,
C
,
patterns
,
lpegmatch
=
lpeg
.
P
,
lpeg
.
C
,
lpeg
.
patterns
,
lpeg
.
match
43
local
formatters
=
string
.
formatters
44
local
urlhashed
,
urlquery
=
url
.
hashed
,
url
.
query
45
local
report
=
application
.
report
46
local
gettime
=
os
.
gettimeofday
or
os
.
clock
47 48
scripts
=
scripts
or
{
}
49
scripts
.
webserver
=
scripts
.
webserver
or
{
}
50 51
local
socket
=
socket
or
require
(
"
socket
"
)
52
----- http = http or require("socket.http") -- not needed
53 54
-- The following two lists are taken from webrick (ruby) and
55
-- extended with a few extra suffixes.
56 57
local
mimetypes
=
{
58
ai
=
'
application/postscript
'
,
59
asc
=
'
text/plain
'
,
60
avi
=
'
video/x-msvideo
'
,
61
bin
=
'
application/octet-stream
'
,
62
bmp
=
'
image/bmp
'
,
63
bz2
=
'
application/x-bzip2
'
,
64
cer
=
'
application/pkix-cert
'
,
65
class
=
'
application/octet-stream
'
,
66
crl
=
'
application/pkix-crl
'
,
67
crt
=
'
application/x-x509-ca-cert
'
,
68
css
=
'
text/css
'
,
69
dms
=
'
application/octet-stream
'
,
70
doc
=
'
application/msword
'
,
71
dvi
=
'
application/x-dvi
'
,
72
eps
=
'
application/postscript
'
,
73
etx
=
'
text/x-setext
'
,
74
exe
=
'
application/octet-stream
'
,
75
gif
=
'
image/gif
'
,
76
gz
=
'
application/x-tar
'
,
77
hqx
=
'
application/mac-binhex40
'
,
78
htm
=
'
text/html
'
,
79
html
=
'
text/html
'
,
80
jpe
=
'
image/jpeg
'
,
81
jpeg
=
'
image/jpeg
'
,
82
jpg
=
'
image/jpeg
'
,
83
lha
=
'
application/octet-stream
'
,
84
lzh
=
'
application/octet-stream
'
,
85
mov
=
'
video/quicktime
'
,
86
mpe
=
'
video/mpeg
'
,
87
mpeg
=
'
video/mpeg
'
,
88
mpg
=
'
video/mpeg
'
,
89
pbm
=
'
image/x-portable-bitmap
'
,
90
pdf
=
'
application/pdf
'
,
91
pgm
=
'
image/x-portable-graymap
'
,
92
png
=
'
image/png
'
,
93
pnm
=
'
image/x-portable-anymap
'
,
94
ppm
=
'
image/x-portable-pixmap
'
,
95
ppt
=
'
application/vnd.ms-powerpoint
'
,
96
ps
=
'
application/postscript
'
,
97
qt
=
'
video/quicktime
'
,
98
ras
=
'
image/x-cmu-raster
'
,
99
rb
=
'
text/plain
'
,
100
rd
=
'
text/plain
'
,
101
rgb
=
'
image/x-rgb
'
,
102
rtf
=
'
application/rtf
'
,
103
sgm
=
'
text/sgml
'
,
104
sgml
=
'
text/sgml
'
,
105
snd
=
'
audio/basic
'
,
106
tar
=
'
application/x-tar
'
,
107
tgz
=
'
application/x-tar
'
,
108
tif
=
'
image/tiff
'
,
109
tiff
=
'
image/tiff
'
,
110
txt
=
'
text/plain
'
,
111
xbm
=
'
image/x-xbitmap
'
,
112
xls
=
'
application/vnd.ms-excel
'
,
113
xml
=
'
text/xml
'
,
114
xpm
=
'
image/x-xpixmap
'
,
115
xwd
=
'
image/x-xwindowdump
'
,
116
zip
=
'
application/zip
'
,
117
}
118 119
local
messages
=
{
120
[
100
]
=
'
Continue
'
,
121
[
101
]
=
'
Switching Protocols
'
,
122
[
200
]
=
'
OK
'
,
123
[
201
]
=
'
Created
'
,
124
[
202
]
=
'
Accepted
'
,
125
[
203
]
=
'
Non-Authoritative Information
'
,
126
[
204
]
=
'
No Content
'
,
127
[
205
]
=
'
Reset Content
'
,
128
[
206
]
=
'
Partial Content
'
,
129
[
300
]
=
'
Multiple Choices
'
,
130
[
301
]
=
'
Moved Permanently
'
,
131
[
302
]
=
'
Found
'
,
132
[
303
]
=
'
See Other
'
,
133
[
304
]
=
'
Not Modified
'
,
134
[
305
]
=
'
Use Proxy
'
,
135
[
307
]
=
'
Temporary Redirect
'
,
136
[
400
]
=
'
Bad Request
'
,
137
[
401
]
=
'
Unauthorized
'
,
138
[
402
]
=
'
Payment Required
'
,
139
[
403
]
=
'
Forbidden
'
,
140
[
404
]
=
'
Not Found
'
,
141
[
405
]
=
'
Method Not Allowed
'
,
142
[
406
]
=
'
Not Acceptable
'
,
143
[
407
]
=
'
Proxy Authentication Required
'
,
144
[
408
]
=
'
Request Timeout
'
,
145
[
409
]
=
'
Conflict
'
,
146
[
410
]
=
'
Gone
'
,
147
[
411
]
=
'
Length Required
'
,
148
[
412
]
=
'
Precondition Failed
'
,
149
[
413
]
=
'
Request Entity Too Large
'
,
150
[
414
]
=
'
Request-URI Too Large
'
,
151
[
415
]
=
'
Unsupported Media Type
'
,
152
[
416
]
=
'
Request Range Not Satisfiable
'
,
153
[
417
]
=
'
Expectation Failed
'
,
154
[
500
]
=
'
Internal Server Error
'
,
155
[
501
]
=
'
Not Implemented
'
,
156
[
502
]
=
'
Bad Gateway
'
,
157
[
503
]
=
'
Service Unavailable
'
,
158
[
504
]
=
'
Gateway Timeout
'
,
159
[
505
]
=
'
HTTP Version Not Supported
'
,
160
}
161 162
local
f_content_length
=
formatters
[
"
Content-Length: %s\r\n
"
]
163
local
f_content_type
=
formatters
[
"
Content-Type: %s\r\n
"
]
164
local
f_error_title
=
formatters
[
"
<head><title>%s %s</title></head><html><h2>%s %s</h2></html>
"
]
165 166
local
handlers
=
{
}
167 168
local
function
errormessage
(
client
,
configuration
,
n
)
169
local
data
=
f_error_title
(
n
,
messages
[
n
]
,
n
,
messages
[
n
]
)
170
report
(
"
handling error %s: %s
"
,
n
,
messages
[
n
]
)
171
handlers
.
generic
(
client
,
configuration
,
data
,
nil
,
true
)
172
end
173 174
local
validpaths
,
registered
=
{
}
,
{
}
175 176
function
scripts
.
webserver
.
registerpath
(
name
)
177
if
not
registered
[
name
]
then
178
local
cleanname
=
gsub
(
name
,
"
%.%.
"
,
"
deleted-parent
"
)
179
report
(
"
registering path: %s
"
,
cleanname
)
180
validpaths
[
#
validpaths
+
1
]
=
cleanname
181
registered
[
name
]
=
true
182
end
183
end
184 185
function
handlers
.
generic
(
client
,
configuration
,
data
,
suffix
,
iscontent
)
186
local
name
=
data
187
if
not
iscontent
then
188
report
(
"
requested file: %s
"
,
name
)
189
local
fullname
=
joinpath
(
configuration
.
root
,
name
)
190
data
=
loaddata
(
fullname
)
or
"
"
191
if
data
=
=
"
"
then
192
for
n
=
1
,
#
validpaths
do
193
local
fullname
=
joinpath
(
validpaths
[
n
]
,
name
)
194
data
=
loaddata
(
fullname
)
or
"
"
195
if
data
~
=
"
"
then
196
report
(
"
sending generic file: %s
"
,
fullname
)
197
break
198
end
199
end
200
else
201
report
(
"
sending generic file: %s
"
,
fullname
)
202
end
203
end
204
if
data
and
data
~
=
"
"
then
205
client
:
send
(
"
HTTP/1.1 200 OK\r\n
"
)
206
client
:
send
(
"
Connection: close\r\n
"
)
207
client
:
send
(
f_content_length
(
#
data
)
)
208
client
:
send
(
f_content_type
(
suffix
and
mimetypes
[
suffix
]
or
"
text/html
"
)
)
209
client
:
send
(
"
Cache-Control: no-cache, no-store, must-revalidate, max-age=0\r\n
"
)
210
client
:
send
(
"
\r\n
"
)
211
client
:
send
(
data
)
212
client
:
send
(
"
\r\n
"
)
213
else
214
report
(
"
unknown file: %s
"
,
tostring
(
name
)
)
215
errormessage
(
client
,
configuration
,
404
)
216
end
217
end
218 219
-- return os.date()
220 221
-- return { content = "crap" }
222 223
-- return function(configuration,filename)
224
-- return { content = filename }
225
-- end
226 227
local
loaded
=
{
}
228 229
function
handlers
.
lua
(
client
,
configuration
,
filename
,
suffix
,
iscontent
,
hashed
)
-- filename will disappear, and become hashed.filename
230
local
filename
=
joinpath
(
configuration
.
scripts
,
filename
)
231
if
not
is_qualified_path
(
filename
)
then
232
filename
=
joinpath
(
configuration
.
root
,
filename
)
233
end
234
-- todo: split url in components, see l-url; rather trivial
235
local
result
,
keep
=
loaded
[
filename
]
,
false
236
if
result
then
237
report
(
"
reusing script: %s
"
,
filename
)
238
else
239
report
(
"
locating script: %s
"
,
filename
)
240
if
lfs
.
isfile
(
filename
)
then
241
report
(
"
loading script: %s
"
,
filename
)
242
result
=
loadfile
(
filename
)
243
report
(
"
return type: %s
"
,
type
(
result
)
)
244
if
result
and
type
(
result
)
=
=
"
function
"
then
245
-- result() should return a table { [type=,] [length=,] content= }, function or string
246
result
,
keep
=
result
(
)
247
if
keep
then
248
report
(
"
saving script: %s
"
,
type
(
result
)
)
249
loaded
[
filename
]
=
result
250
end
251
end
252
else
253
report
(
"
problematic script: %s
"
,
filename
)
254
errormessage
(
client
,
configuration
,
404
)
255
end
256
end
257
if
result
then
258
if
type
(
result
)
=
=
"
function
"
then
259
report
(
"
running script: %s
"
,
filename
)
260
result
=
result
(
configuration
,
filename
,
hashed
)
-- second argument will become query
261
end
262
if
result
and
type
(
result
)
=
=
"
string
"
then
263
result
=
{
content
=
result
}
264
end
265
if
result
and
type
(
result
)
=
=
"
table
"
then
266
if
result
.
content
then
267
local
suffix
=
result
.
type
or
"
text/html
"
268
local
action
=
handlers
[
suffix
]
or
handlers
.
generic
269
action
(
client
,
configuration
,
result
.
content
,
suffix
,
true
)
-- content
270
elseif
result
.
filename
then
271
local
suffix
=
filesuffix
(
result
.
filename
)
or
"
text/html
"
272
local
action
=
handlers
[
suffix
]
or
handlers
.
generic
273
action
(
client
,
configuration
,
result
.
filename
,
suffix
,
false
)
-- filename
274
else
275
report
(
"
no content of filename in result
"
)
276
errormessage
(
client
,
configuration
,
404
)
277
end
278
else
279
report
(
"
no valid result
"
)
280
errormessage
(
client
,
configuration
,
500
)
281
end
282
else
283
report
(
"
no result
"
)
284
errormessage
(
client
,
configuration
,
404
)
285
end
286
end
287 288
handlers
.
luc
=
handlers
.
lua
289
handlers
.
html
=
handlers
.
htm
290 291
local
indices
=
{
"
index.htm
"
,
"
index.html
"
}
292
local
portnumber
=
8088
293 294
local
newline
=
patterns
.
newline
295
local
spacer
=
patterns
.
spacer
296
local
whitespace
=
patterns
.
whitespace
297
local
method
=
P
(
"
GET
"
)
298
+
P
(
"
POST
"
)
299
local
identify
=
(
1
-
method
)
^
0
300
*
C
(
method
)
301
*
spacer
^
1
302
*
C
(
(
1
-
spacer
)
^
1
)
303
*
spacer
^
1
304
*
P
(
"
HTTP/
"
)
305
*
(
1
-
whitespace
)
^
0
306
*
C
(
P
(
1
)
^
0
)
307 308
function
scripts
.
webserver
.
run
(
configuration
)
309
-- check configuration
310
configuration
.
port
=
tonumber
(
configuration
.
port
or
os
.
getenv
(
"
MTX_SERVER_PORT
"
)
or
portnumber
)
or
portnumber
311
if
not
configuration
.
root
or
not
lfs
.
isdir
(
configuration
.
root
)
then
312
configuration
.
root
=
os
.
getenv
(
"
MTX_SERVER_ROOT
"
)
or
"
.
"
313
end
314
-- locate root and index file in tex tree
315
if
not
lfs
.
isdir
(
configuration
.
root
)
then
316
for
i
=
1
,
#
indices
do
317
local
name
=
indices
[
i
]
318
local
root
=
resolvers
.
resolve
(
"
path:
"
.
.
name
)
or
"
"
319
if
root
~
=
"
"
then
320
configuration
.
root
=
root
321
configuration
.
index
=
configuration
.
index
or
name
322
break
323
end
324
end
325
end
326
configuration
.
root
=
dir
.
expandname
(
configuration
.
root
)
327
if
not
configuration
.
index
then
328
for
i
=
1
,
#
indices
do
329
local
name
=
indices
[
i
]
330
if
lfs
.
isfile
(
joinpath
(
configuration
.
root
,
name
)
)
then
331
configuration
.
index
=
name
-- we will prepend the rootpath later
332
break
333
end
334
end
335
configuration
.
index
=
configuration
.
index
or
"
unknown
"
336
end
337
if
not
configuration
.
scripts
or
configuration
.
scripts
=
=
"
"
then
338
configuration
.
scripts
=
dir
.
expandname
(
joinpath
(
configuration
.
root
or
"
.
"
,
configuration
.
scripts
or
"
.
"
)
)
339
end
340
-- so far for checks
341
report
(
"
running at port: %s
"
,
configuration
.
port
)
342
report
(
"
document root: %s
"
,
configuration
.
root
or
resolvers
.
ownpath
)
343
report
(
"
main index file: %s
"
,
configuration
.
index
)
344
report
(
"
scripts subpath: %s
"
,
configuration
.
scripts
)
345
report
(
"
context services: http://localhost:%s/mtx-server-ctx-startup.lua
"
,
configuration
.
port
)
346
local
server
=
assert
(
socket
.
bind
(
"
*
"
,
configuration
.
port
)
)
347
local
script
=
configuration
.
script
348
while
true
do
-- blocking
349
-- local start = gettime()
350
local
client
=
server
:
accept
(
)
351
client
:
settimeout
(
configuration
.
timeout
or
60
)
352
local
request
,
e
=
client
:
receive
(
)
353
if
e
then
354
-- probably a time out
355
-- errormessage(client,configuration,404)
356
else
357
local
from
=
client
:
getpeername
(
)
358
report
(
"
request from: %s
"
,
tostring
(
from
)
)
359
report
(
"
request data: %s
"
,
tostring
(
request
)
)
360
-- local fullurl = match(request,"(GET) (.+) HTTP/.*$") or "" -- todo: more clever / post
361
-- if fullurl == "" then
362
local
method
,
fullurl
,
body
=
lpegmatch
(
identify
,
request
)
363
if
method
=
=
"
"
or
fullurl
=
=
"
"
then
364
report
(
"
no url
"
)
365
errormessage
(
client
,
configuration
,
404
)
366
else
367 368
-- todo: method: POST
369 370
fullurl
=
url
.
unescapeget
(
fullurl
)
371
report
(
"
requested url: %s
"
,
fullurl
)
372
-- fullurl = socket.url.unescape(fullurl) -- happens later
373
local
hashed
=
urlhashed
(
fullurl
)
374
local
query
=
urlquery
(
hashed
.
query
)
375
local
filename
=
hashed
.
path
-- hm, not query?
376
hashed
.
body
=
body
377
if
script
then
378
filename
=
script
379
report
(
"
forced script: %s
"
,
filename
)
380
local
suffix
=
filesuffix
(
filename
)
381
local
action
=
handlers
[
suffix
]
or
handlers
.
generic
382
if
action
then
383
report
(
"
performing action: %s
"
,
filename
)
384
action
(
client
,
configuration
,
filename
,
suffix
,
false
,
hashed
)
-- filename and no content
385
else
386
report
(
"
invalid action: %s
"
,
filename
)
387
errormessage
(
client
,
configuration
,
404
)
388
end
389
elseif
filename
then
390
local
rawname
=
socket
.
url
.
unescape
(
filename
)
391
filename
=
rawname
392
report
(
"
requested action: %s
"
,
filename
or
"
?
"
)
393
if
find
(
filename
,
"
%.%.
"
)
then
394
filename
=
nil
-- invalid path
395
end
396
if
filename
=
=
nil
or
filename
=
=
"
"
or
filename
=
=
"
/
"
then
397
filename
=
configuration
.
index
398
report
(
"
invalid filename, forcing: %s
"
,
filename
)
399
end
400
local
suffix
=
filesuffix
(
filename
)
401
local
action
=
handlers
[
suffix
]
or
handlers
.
generic
402
if
action
then
403
report
(
"
performing action: %s
"
,
filename
or
"
?
"
)
404
action
(
client
,
configuration
,
filename
,
suffix
,
false
,
hashed
)
-- filename and no content
405
else
406
report
(
"
invalid action: %s
"
,
filename
or
"
?
"
)
407
errormessage
(
client
,
configuration
,
404
)
408
end
409
else
410
report
(
"
invalid request
"
)
411
errormessage
(
client
,
configuration
,
404
)
412
end
413
end
414
end
415
client
:
close
(
)
416
-- report("time spent with client: %0.03f seconds",gettime()-start)
417
end
418
end
419 420
if
environment
.
argument
(
"
auto
"
)
then
421
local
path
=
resolvers
.
findfile
(
"
mtx-server.lua
"
)
or
"
.
"
422
scripts
.
webserver
.
run
{
423
port
=
environment
.
argument
(
"
port
"
)
,
424
root
=
environment
.
argument
(
"
root
"
)
or
dirname
(
path
)
or
"
.
"
,
425
scripts
=
environment
.
argument
(
"
scripts
"
)
or
dirname
(
path
)
or
"
.
"
,
426
script
=
environment
.
argument
(
"
script
"
)
,
427
}
428
elseif
environment
.
argument
(
"
start
"
)
then
429
scripts
.
webserver
.
run
{
430
port
=
environment
.
argument
(
"
port
"
)
,
431
root
=
environment
.
argument
(
"
root
"
)
or
"
.
"
,
-- "e:/websites/www.pragma-ade.com",
432
index
=
environment
.
argument
(
"
index
"
)
,
433
scripts
=
environment
.
argument
(
"
scripts
"
)
,
434
script
=
environment
.
argument
(
"
script
"
)
,
435
}
436
elseif
environment
.
argument
(
"
exporthelp
"
)
then
437
application
.
export
(
environment
.
argument
(
"
exporthelp
"
)
,
environment
.
files
[
1
]
)
438
else
439
application
.
help
(
)
440
end
441 442
-- mtxrun --script server --start => http://localhost:8088/mtx-server-ctx-startup.lua
443