mirror of
https://github.com/jart/cosmopolitan.git
synced 2025-05-22 21:32:31 +00:00
Support OnHttpRequest Lua callback
If your redbean `/.init.lua` file defines a global callable named `OnHttpRequest` then redbean will delegate all serving control to your function. You may then restore the default serving paths, by calling the new `Route()`, `RouteHost()`, and `RoutePath()` APIs. Closes #150
This commit is contained in:
parent
dc6d11a031
commit
472b95fea3
13 changed files with 671 additions and 568 deletions
17
tool/net/demo/.init.lua
Normal file
17
tool/net/demo/.init.lua
Normal file
|
@ -0,0 +1,17 @@
|
|||
-- /.init.lua is loaded at startup in redbean's main process
|
||||
|
||||
HidePath('/usr/share/zoneinfo/')
|
||||
|
||||
function OnHttpRequest()
|
||||
if HasParam('magic') then
|
||||
Write('<p>\r\n')
|
||||
Write('OnHttpRequest() has intercepted your request<br>\r\n')
|
||||
Write('because you specified the magic parameter\r\n')
|
||||
Write('<pre>\r\n')
|
||||
Write(EscapeHtml(LoadAsset('/.init.lua')))
|
||||
Write('</pre>\r\n')
|
||||
else
|
||||
Route()
|
||||
end
|
||||
SetHeader('Server', 'redbean!')
|
||||
end
|
1
tool/net/demo/.reload.lua
Normal file
1
tool/net/demo/.reload.lua
Normal file
|
@ -0,0 +1 @@
|
|||
-- special script called by main redbean process on invalidate
|
11
tool/net/demo/404.html
Normal file
11
tool/net/demo/404.html
Normal file
|
@ -0,0 +1,11 @@
|
|||
<!doctype html>
|
||||
<title>404 not found</title>
|
||||
|
||||
<pre aria-label="404 not found">
|
||||
_ _ ___ _ _ _ __ _
|
||||
| || | / _ \| || | _ __ ___ | |_ / _| ___ _ _ _ __ __| |
|
||||
| || |_| | | | || |_ | '_ \ / _ \| __| | |_ / _ \| | | | '_ \ / _` |
|
||||
|__ _| |_| |__ _| | | | | (_) | |_ | _| (_) | |_| | | | | (_| |
|
||||
|_| \___/ |_| |_| |_|\___/ \__| |_| \___/ \__,_|_| |_|\__,_|
|
||||
|
||||
</pre>
|
1
tool/net/demo/hello.lua
Normal file
1
tool/net/demo/hello.lua
Normal file
|
@ -0,0 +1 @@
|
|||
Write('hello world\r\n')
|
29
tool/net/demo/index.html
Normal file
29
tool/net/demo/index.html
Normal file
|
@ -0,0 +1,29 @@
|
|||
<!doctype html>
|
||||
<meta charset="utf-8">
|
||||
<title>redbean</title>
|
||||
<link rel="stylesheet" href="redbean.css">
|
||||
<img src="/redbean.png" class="logo" width="84" height="84">
|
||||
|
||||
<h2>
|
||||
<big>redbean</big><br>
|
||||
<small>distributable static web server</small>
|
||||
</h2>
|
||||
|
||||
<p>
|
||||
Here's what you need to know about redbean:
|
||||
|
||||
<ul>
|
||||
<li>million qps on modern pc
|
||||
<li>container is executable zip file
|
||||
<li>userspace filesystem w/o syscall overhead
|
||||
<li>kernelspace zero-copy gzip encoded responses
|
||||
<li>executable runs on linux, bsd, mac, and windows
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
redbean is based on <a href="https://justine.storage.googleapis.com/ape.html">αcτµαlly pδrταblε εxεcµταblε</a>
|
||||
and <a href="https://github.com/jart/cosmopolitan">cosmopolitan</a>.
|
||||
<br>
|
||||
redbean is authored by Justine Tunney who's on
|
||||
<a href="https://github.com/jart">GitHub</a> and
|
||||
<a href="https://twitter.com/JustineTunney">Twitter</a>.
|
70
tool/net/demo/redbean-form.lua
Normal file
70
tool/net/demo/redbean-form.lua
Normal file
|
@ -0,0 +1,70 @@
|
|||
-- redbean post request handler demo
|
||||
|
||||
local function main()
|
||||
if GetMethod() ~= 'POST' then
|
||||
ServeError(405)
|
||||
SetHeader('Allow', 'POST')
|
||||
return
|
||||
end
|
||||
SetStatus(200)
|
||||
SetHeader('Content-Type', 'text/html; charset=utf-8')
|
||||
Write('<!doctype html>\r\n')
|
||||
Write('<title>redbean</title>\r\n')
|
||||
Write('<h3>POST Request HTML Form Handler Demo</h3>\r\n')
|
||||
|
||||
Write('<p>')
|
||||
firstname = GetParam('firstname')
|
||||
lastname = GetParam('lastname')
|
||||
if firstname and lastname then
|
||||
Write('Hello ')
|
||||
Write(EscapeHtml(VisualizeControlCodes(firstname)))
|
||||
Write(' ')
|
||||
Write(EscapeHtml(VisualizeControlCodes(lastname)))
|
||||
Write('!<br>')
|
||||
Write('Thank you for using redbean.')
|
||||
end
|
||||
|
||||
Write('<dl>\r\n')
|
||||
|
||||
Write('<dt>Params\r\n')
|
||||
Write('<dd>\r\n')
|
||||
Write('<dl>\r\n')
|
||||
params = GetParams()
|
||||
for i = 1,#params do
|
||||
Write('<dt>')
|
||||
Write(EscapeHtml(VisualizeControlCodes(params[i][1])))
|
||||
Write('\r\n')
|
||||
if params[i][2] then
|
||||
Write('<dd>')
|
||||
Write(EscapeHtml(VisualizeControlCodes(params[i][2])))
|
||||
Write('\r\n')
|
||||
end
|
||||
end
|
||||
Write('</dl>\r\n')
|
||||
|
||||
Write('<dt>Headers\r\n')
|
||||
Write('<dd>\r\n')
|
||||
Write('<dl>\r\n')
|
||||
for k,v in pairs(GetHeaders()) do
|
||||
Write('<dt>')
|
||||
Write(EscapeHtml(k))
|
||||
Write('\r\n')
|
||||
Write('<dd>')
|
||||
Write(EscapeHtml(v))
|
||||
Write('\r\n')
|
||||
end
|
||||
Write('</dl>\r\n')
|
||||
|
||||
Write('<dt>Payload\r\n')
|
||||
Write('<dd><p>')
|
||||
Write(EscapeHtml(VisualizeControlCodes(GetPayload())))
|
||||
Write('\r\n')
|
||||
|
||||
Write('</dl>\r\n')
|
||||
|
||||
Write('<p>')
|
||||
Write('<a href="redbean.lua">Click here</a> ')
|
||||
Write('to return to the previous page.\r\n')
|
||||
end
|
||||
|
||||
main()
|
8
tool/net/demo/redbean-xhr.lua
Normal file
8
tool/net/demo/redbean-xhr.lua
Normal file
|
@ -0,0 +1,8 @@
|
|||
-- redbean xhr handler demo
|
||||
hdr = GetHeader('x-custom-header')
|
||||
if hdr then
|
||||
SetHeader('Vary', 'X-Custom-Header')
|
||||
SetHeader('X-Custom-Header', 'hello ' .. hdr)
|
||||
else
|
||||
ServeError(400)
|
||||
end
|
43
tool/net/demo/redbean.css
Normal file
43
tool/net/demo/redbean.css
Normal file
|
@ -0,0 +1,43 @@
|
|||
html {
|
||||
font-family: sans-serif;
|
||||
font-size: 14pt;
|
||||
}
|
||||
|
||||
body {
|
||||
max-width: 700px;
|
||||
min-width: 700px;
|
||||
margin: 0 auto 0 auto;
|
||||
}
|
||||
|
||||
pre,
|
||||
code {
|
||||
font-family: monospace;
|
||||
font-size: 12pt;
|
||||
}
|
||||
|
||||
p {
|
||||
text-align: justify;
|
||||
}
|
||||
|
||||
.indent {
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2 {
|
||||
margin: 1.5em 0 1.5em 0;
|
||||
}
|
||||
|
||||
h1 strong {
|
||||
font-size: 14pt;
|
||||
}
|
||||
|
||||
footer {
|
||||
margin-top: 12em;
|
||||
margin-bottom: 3em;
|
||||
font-size: 14pt;
|
||||
}
|
||||
|
||||
.logo {
|
||||
float: right;
|
||||
}
|
350
tool/net/demo/redbean.lua
Normal file
350
tool/net/demo/redbean.lua
Normal file
|
@ -0,0 +1,350 @@
|
|||
-- redbean lua server page demo
|
||||
|
||||
local function main()
|
||||
-- This is the best way to print data to the console or log file.
|
||||
Log(kLogWarn, "hello from \e[1mlua\e[0m!")
|
||||
|
||||
-- This check is pedantic but might be good to have.
|
||||
if GetMethod() ~= 'GET' and GetMethod() ~= 'HEAD' then
|
||||
ServeError(405)
|
||||
SetHeader('Allow', 'GET, HEAD')
|
||||
return
|
||||
end
|
||||
|
||||
-- These two lines are optional.
|
||||
-- The default behavior is to do this if you don't.
|
||||
SetStatus(200) -- Shorthand for SetStatus(200, "OK")
|
||||
SetHeader('Content-Type', 'text/html; charset=utf-8')
|
||||
|
||||
-- Response data is buffered until the script finishes running.
|
||||
-- Compression is applied automatically, based on your headers.
|
||||
Write('<!doctype html>\r\n')
|
||||
Write('<title>redbean</title>\r\n')
|
||||
Write('<h1>\r\n')
|
||||
Write('<img style="vertical-align:middle" src="data:image/png;base64,\r\n')
|
||||
Write(EncodeBase64(LoadAsset('/redbean.png')))
|
||||
Write('">\r\n')
|
||||
Write('redbean lua server page demo\r\n')
|
||||
Write('</h1>\r\n')
|
||||
|
||||
-- Prevent caching.
|
||||
-- We need this because we're doing things like putting the client's
|
||||
-- IP address in the response so we naturally don't want that cached
|
||||
SetHeader('Expires', FormatHttpDateTime(GetDate()))
|
||||
SetHeader('Cache-Control', 'no-cache, must-revalidate, max-age=0')
|
||||
|
||||
-- GetUrl() is the resolved Request-URI (TODO: Maybe change API to return a URL object?)
|
||||
Write('<p>Thank you for visiting <code>')
|
||||
Write(GetUrl()) -- redbean encoded this value so it doesn't need html entity escaping
|
||||
Write('</code>\r\n')
|
||||
|
||||
-- GetParam(NAME) is the fastest easiest way to get URL and FORM params
|
||||
-- If you want the RequestURL query params specifically in full do this
|
||||
Write('<h3>request url parameters</h3>\r\n')
|
||||
params = ParseUrl(GetUrl()).params -- like GetParams() but w/o form body
|
||||
if params and #params>0 then
|
||||
Write('<dl>\r\n')
|
||||
for i = 1,#params do
|
||||
Write('<dt>')
|
||||
Write(EscapeHtml(VisualizeControlCodes(params[i][1])))
|
||||
Write('\r\n')
|
||||
if params[i][2] then
|
||||
Write('<dd>')
|
||||
Write(EscapeHtml(VisualizeControlCodes(params[i][2])))
|
||||
Write('\r\n')
|
||||
end
|
||||
end
|
||||
Write('</dl>\r\n')
|
||||
Write("<p>Whatever you do, don't click on ")
|
||||
Write('<a href="')
|
||||
Write(EscapeHtml(EscapePath(GetPath()) .. '?magic'))
|
||||
Write('">')
|
||||
Write(EscapeHtml(VisualizeControlCodes(GetPath())))
|
||||
Write('?magic</a>\r\n')
|
||||
else
|
||||
Write('<p>\r\n')
|
||||
Write('<em>none</em><br>\r\n')
|
||||
Write('ProTip: Try <a href="')
|
||||
Write(EscapeHtml(EscapePath(GetPath()) .. '?x=hi%20there%00%C0%80&y&z&z=' .. EscapeParam('&')))
|
||||
Write('">clicking here</a>!\r\n')
|
||||
end
|
||||
|
||||
-- redbean command line arguments
|
||||
-- these come *after* the c getopt server arguments
|
||||
Write('<h3>command line arguments</h3>\r\n')
|
||||
if #argv > 0 then
|
||||
Write('<ul>\r\n')
|
||||
for i = 1,#argv do
|
||||
Write('<li>')
|
||||
Write(EscapeHtml(VisualizeControlCodes(argv[i])))
|
||||
Write('\r\n')
|
||||
end
|
||||
Write('</ul>\r\n')
|
||||
else
|
||||
Write('<p><em>none</em>\r\n')
|
||||
end
|
||||
|
||||
Write([[
|
||||
<h3>post request html form demo</h3>
|
||||
<form action="redbean-form.lua" method="post">
|
||||
<input type="text" id="firstname" name="firstname">
|
||||
<label for="firstname">first name</label>
|
||||
<br>
|
||||
<input type="text" id="lastname" name="lastname">
|
||||
<label for="lastname">last name</label>
|
||||
<br>
|
||||
<input type="submit" value="Submit">
|
||||
</form>
|
||||
]])
|
||||
|
||||
Write([[
|
||||
<h3>xmlhttprequest request demo</h3>
|
||||
<input id="x" value="lâtìn1">
|
||||
<label for="x">X-Custom-Header</label><br>
|
||||
<button id="send">send</button><br>
|
||||
<div id="result"></div>
|
||||
<script>
|
||||
function OnSend() {
|
||||
var r = new XMLHttpRequest();
|
||||
r.onload = function() {
|
||||
document.getElementById("result").innerText = this.getResponseHeader('X-Custom-Header');
|
||||
};
|
||||
r.open('POST', 'redbean-xhr.lua');
|
||||
r.setRequestHeader('X-Custom-Header', document.getElementById('x').value);
|
||||
r.send();
|
||||
}
|
||||
document.getElementById('send').onclick = OnSend;
|
||||
</script>
|
||||
]])
|
||||
|
||||
-- fast redbean apis for accessing already parsed request data
|
||||
Write('<h3>extra information</h3>\r\n')
|
||||
Write('<dl>\r\n')
|
||||
Write('<dt>GetMethod()\r\n')
|
||||
Write('<dd>')
|
||||
Write(EscapeHtml(GetMethod())) -- & and ' are legal in http methods
|
||||
Write('\r\n')
|
||||
if GetUser() then
|
||||
Write('<dt>GetUser()\r\n')
|
||||
Write('<dd>')
|
||||
Write(EscapeHtml(VisualizeControlCodes(GetUser())))
|
||||
Write('\r\n')
|
||||
end
|
||||
if GetScheme() then
|
||||
Write('<dt>GetScheme()\r\n')
|
||||
Write('<dd>')
|
||||
Write(GetScheme())
|
||||
Write('\r\n')
|
||||
end
|
||||
if GetPass() then
|
||||
Write('<dt>GetPass()\r\n')
|
||||
Write('<dd>')
|
||||
Write(EscapeHtml(VisualizeControlCodes(GetPass())))
|
||||
Write('\r\n')
|
||||
end
|
||||
Write('<dt>GetHost() <small>(from HTTP Request-URI or Host header or X-Forwarded-Host header or Berkeley Sockets)</small>\r\n')
|
||||
Write('<dd>')
|
||||
Write(EscapeHtml(VisualizeControlCodes(GetHost())))
|
||||
Write('\r\n')
|
||||
Write('<dt>GetPort() <small>(from HTTP Request-URI or Host header or X-Forwarded-Host header or Berkeley Sockets)</small>\r\n')
|
||||
Write('<dd>')
|
||||
Write(tostring(GetPort()))
|
||||
Write('\r\n')
|
||||
Write('<dt>GetPath() <small>(from HTTP Request-URI)</small>\r\n')
|
||||
Write('<dd>')
|
||||
Write(EscapeHtml(VisualizeControlCodes(GetPath())))
|
||||
Write('\r\n')
|
||||
Write('<dt>GetEffectivePath() <small>(actual path used internally to load the lua asset: routed depending on host, request path, and rewrites)</small>\r\n')
|
||||
Write('<dd>')
|
||||
Write(EscapeHtml(VisualizeControlCodes(GetEffectivePath())))
|
||||
Write('\r\n')
|
||||
if GetFragment() then
|
||||
Write('<dt>GetFragment()\r\n')
|
||||
Write('<dd>')
|
||||
Write(EscapeHtml(VisualizeControlCodes(GetFragment())))
|
||||
Write('\r\n')
|
||||
end
|
||||
Write('<dt>GetRemoteAddr() <small>(from Bekeley Sockets or X-Forwarded-For header)</small>\r\n')
|
||||
Write('<dd>')
|
||||
ip, port = GetRemoteAddr()
|
||||
Write(string.format('%s, %d', FormatIp(ip), port))
|
||||
if CategorizeIp(ip) then
|
||||
Write('<br>\r\n')
|
||||
Write(CategorizeIp(ip))
|
||||
end
|
||||
Write('\r\n')
|
||||
Write('<dt>GetClientAddr()\r\n')
|
||||
Write('<dd>')
|
||||
ip, port = GetClientAddr()
|
||||
Write(string.format('%s, %d', FormatIp(ip), port))
|
||||
if CategorizeIp(ip) then
|
||||
Write('<br>\r\n')
|
||||
Write(CategorizeIp(ip))
|
||||
end
|
||||
Write('\r\n')
|
||||
Write('<dt>GetServerIp()\r\n')
|
||||
Write('<dd>')
|
||||
ip, port = GetServerAddr()
|
||||
Write(string.format('%s, %d', FormatIp(ip), port))
|
||||
if CategorizeIp(ip) then
|
||||
Write('<br>\r\n')
|
||||
Write(CategorizeIp(ip))
|
||||
end
|
||||
Write('\r\n')
|
||||
Write('</dl>\r\n')
|
||||
|
||||
-- redbean apis for generalized parsing and encoding
|
||||
referer = GetHeader('Referer')
|
||||
if referer then
|
||||
url = ParseUrl(referer)
|
||||
if url.scheme then
|
||||
url.scheme = string.upper(url.scheme)
|
||||
end
|
||||
Write('<h3>referer url</h3>\r\n')
|
||||
Write('<p>\r\n')
|
||||
Write(EscapeHtml(EncodeUrl(url)))
|
||||
Write('<dl>\r\n')
|
||||
if url.scheme then
|
||||
Write('<dt>scheme\r\n')
|
||||
Write('<dd>\r\n')
|
||||
Write(url.scheme)
|
||||
end
|
||||
if url.user then
|
||||
Write('<dt>user\r\n')
|
||||
Write('<dd>\r\n')
|
||||
Write(EscapeHtml(VisualizeControlCodes(url.user)))
|
||||
end
|
||||
if url.pass then
|
||||
Write('<dt>pass\r\n')
|
||||
Write('<dd>\r\n')
|
||||
Write(EscapeHtml(VisualizeControlCodes(url.pass)))
|
||||
end
|
||||
if url.host then
|
||||
Write('<dt>host\r\n')
|
||||
Write('<dd>\r\n')
|
||||
Write(EscapeHtml(VisualizeControlCodes(url.host)))
|
||||
end
|
||||
if url.port then
|
||||
Write('<dt>port\r\n')
|
||||
Write('<dd>\r\n')
|
||||
Write(EscapeHtml(VisualizeControlCodes(url.port)))
|
||||
end
|
||||
if url.path then
|
||||
Write('<dt>path\r\n')
|
||||
Write('<dd>\r\n')
|
||||
Write(EscapeHtml(VisualizeControlCodes(url.path)))
|
||||
end
|
||||
if url.params then
|
||||
Write('<dt>params\r\n')
|
||||
Write('<dd>\r\n')
|
||||
Write('<dl>\r\n')
|
||||
for i = 1,#url.params do
|
||||
Write('<dt>')
|
||||
Write(EscapeHtml(VisualizeControlCodes(url.params[i][1])))
|
||||
Write('\r\n')
|
||||
if url.params[i][2] then
|
||||
Write('<dd>')
|
||||
Write(EscapeHtml(VisualizeControlCodes(url.params[i][2])))
|
||||
Write('\r\n')
|
||||
end
|
||||
end
|
||||
Write('</dl>\r\n')
|
||||
end
|
||||
if url.fragment then
|
||||
Write('<dt>fragment\r\n')
|
||||
Write('<dd>\r\n')
|
||||
Write(EscapeHtml(VisualizeControlCodes(url.fragment)))
|
||||
end
|
||||
Write('</dl>\r\n')
|
||||
end
|
||||
|
||||
Write('<h3>posix extended regular expressions</h3>\r\n')
|
||||
s = 'my ' .. FormatIp(GetRemoteAddr()) .. ' ip'
|
||||
-- traditional regular expressions
|
||||
m,a,b,c,d = re.search([[\([0-9]*\)\.\([0-9]*\)\.\([0-9]*\)\.\([0-9]*\)]], s, re.BASIC)
|
||||
-- easy api (~10x slower because compile is O(2ⁿ) and search is O(n))
|
||||
m,a,b,c,d = re.search([[([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})]], s)
|
||||
-- proper api (especially if you put the re.compile() line in /.init.lua)
|
||||
pat = re.compile([[([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})]])
|
||||
m,a,b,c,d = pat:search(s) -- m and rest are nil if match not found
|
||||
Write('<pre>\r\n')
|
||||
Write([[pat = re.compile('([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})')]])
|
||||
Write(string.format('\r\nm,a,b,c,d = pat:search(%q)\r\n', s))
|
||||
Write('</pre>\r\n')
|
||||
Write('<dl>\r\n')
|
||||
Write('<dt>m\r\n')
|
||||
Write('<dd>')
|
||||
Write(tostring(m))
|
||||
Write('\r\n')
|
||||
Write('<dt>a\r\n')
|
||||
Write('<dd>')
|
||||
Write(tostring(a))
|
||||
Write('\r\n')
|
||||
Write('<dt>b\r\n')
|
||||
Write('<dd>')
|
||||
Write(tostring(b))
|
||||
Write('\r\n')
|
||||
Write('<dt>c\r\n')
|
||||
Write('<dd>')
|
||||
Write(tostring(c))
|
||||
Write('\r\n')
|
||||
Write('<dt>d\r\n')
|
||||
Write('<dd>')
|
||||
Write(tostring(d))
|
||||
Write('\r\n')
|
||||
Write('</dl>\r\n')
|
||||
|
||||
Write('<h3>source code to this page</h3>\r\n')
|
||||
Write('<pre>\r\n')
|
||||
Write(EscapeHtml(LoadAsset(GetEffectivePath())))
|
||||
Write('</pre>\r\n')
|
||||
|
||||
-- redbean zip assets
|
||||
Write('<h3>zip assets</h3>\r\n')
|
||||
paths = GetZipPaths()
|
||||
if #paths > 0 then
|
||||
Write('<ul>\r\n')
|
||||
for i = 1,#paths do
|
||||
Write('<li>\r\n')
|
||||
Write('<a href="')
|
||||
Write(EscapeHtml(EscapePath(paths[i])))
|
||||
Write('">')
|
||||
Write(EscapeHtml(VisualizeControlCodes(paths[i])))
|
||||
Write('</a>')
|
||||
if IsHiddenPath(paths[i]) then
|
||||
Write(' <small>[HIDDEN]</small>')
|
||||
end
|
||||
if not IsAcceptablePath(paths[i]) then
|
||||
Write(' <small>[BLOCKED]</small>')
|
||||
end
|
||||
if not IsCompressed(paths[i]) then
|
||||
Write(' <small>[UNCOMPRESSED]</small>')
|
||||
end
|
||||
if (GetAssetMode(paths[i]) & 0xF000) == 0x4000 then
|
||||
Write(' <small>[DIRECTORY]</small>')
|
||||
end
|
||||
Write('<br>\r\n')
|
||||
Write('Modified: ')
|
||||
Write(FormatHttpDateTime(GetLastModifiedTime(paths[i])))
|
||||
Write('<br>\r\n')
|
||||
Write('Mode: ')
|
||||
Write(string.format("0%o", GetAssetMode(paths[i])))
|
||||
Write('<br>\r\n')
|
||||
Write('Size: ')
|
||||
Write(tostring(GetAssetSize(paths[i])))
|
||||
Write('<br>\r\n')
|
||||
if GetComment(paths[i]) then
|
||||
Write('Comment: ')
|
||||
Write(EscapeHtml(VisualizeControlCodes(GetComment(paths[i]))))
|
||||
Write('<br>\r\n')
|
||||
end
|
||||
Write('\r\n')
|
||||
end
|
||||
Write('</ul>\r\n')
|
||||
else
|
||||
Write('<p><em>none</em>\r\n')
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
main()
|
26
tool/net/demo/seekable.txt
Normal file
26
tool/net/demo/seekable.txt
Normal file
|
@ -0,0 +1,26 @@
|
|||
A
|
||||
B
|
||||
C
|
||||
D
|
||||
E
|
||||
F
|
||||
G
|
||||
H
|
||||
I
|
||||
J
|
||||
K
|
||||
L
|
||||
M
|
||||
N
|
||||
O
|
||||
P
|
||||
Q
|
||||
R
|
||||
S
|
||||
T
|
||||
U
|
||||
V
|
||||
W
|
||||
X
|
||||
Y
|
||||
Z
|
Loading…
Add table
Add a link
Reference in a new issue