c{0: 0 (((default-space space-address)) <- ((new)) ((space literal)) ((30 literal) (capacity))) -- nil c{0: 1 (((screen terminal-address)) <- ((init-fake-terminal)) ((20 literal)) ((10 literal))) -- nil c{0: 2 (((5 string-address) (raw)) <- ((get)) ((screen terminal-address) (deref)) ((data offset))) -- nil c{1: 0 ✓ (((default-space space-address)) <- ((new)) ((space literal)) ((30 literal) (capacity))) c{1: 1 ✓ (((screen terminal-address)) <- ((init-fake-terminal)) ((20 literal)) ((10 literal))) c{1: 2 ✓ (((5 string-address) (raw)) <- ((get)) ((screen terminal-address) (deref)) ((data offset))) cn0: convert-names in main cn0: (((default-space space-address)) <- ((new)) ((space literal)) ((30 literal) (capacity))) nil nil cn0: checking arg ((space literal)) cn0: checking arg ((30 literal) (capacity)) cn0: checking oarg ((default-space space-address)) maybe-add: ((default-space space-address)) cn0: (((screen terminal-address)) <- ((init-fake-terminal)) ((20 literal)) ((10 literal))) nil nil cn0: checking arg ((20 literal)) cn0: checking arg ((10 literal)) cn0: checking oarg ((screen terminal-address)) maybe-add: ((screen terminal-address)) cn0: location for oarg ((screen terminal-address)): 1 cn0: (((5 string-address) (raw)) <- ((get)) ((screen terminal-address) (deref)) ((data offset))) ((screen 1)) nil cn0: field-access data in screenterminal-addressderef of type terminal cn0: new field; computing location cn0: field location 4 cn0: checking arg ((screen terminal-address) (deref)) maybe-add: ((screen terminal-address) (deref)) cn0: checking arg ((data offset)) cn0: checking oarg ((5 string-address) (raw)) maybe-add: ((5 string-address) (raw)) cn1: (((default-space space-address)) <- ((new)) ((space literal)) ((30 literal) (capacity))) cn1: (((1 terminal-address)) <- ((init-fake-terminal)) ((20 literal)) ((10 literal))) cn1: (((5 string-address) (raw)) <- ((get)) ((1 terminal-address) (deref)) ((4 offset))) schedule: main run: main 0: (((default-space space-address)) <- ((new)) ((space literal)) ((30 literal) (capacity))) run: main 0: 1000 => ((default-space space-address)) run: main 1: (((1 terminal-address)) <- ((init-fake-terminal)) ((20 literal)) ((10 literal))) run: init-fake-terminal/main 0: (((default-space space-address)) <- ((new)) ((space literal)) ((30 literal) (capacity))) run: init-fake-terminal/main 0: 1031 => ((default-space space-address)) run: init-fake-terminal/main 1: (((1 terminal-address)) <- ((new)) ((terminal literal))) run: init-fake-terminal/main 1: 1062 => ((1 terminal-address)) mem: ((1 terminal-address)): 1033 <= 1062 run: init-fake-terminal/main 2: (((2 integer-address)) <- ((get-address)) ((1 terminal-address) (deref)) ((1 offset))) run: init-fake-terminal/main 2: 1063 => ((2 integer-address)) mem: ((2 integer-address)): 1034 <= 1063 run: init-fake-terminal/main 3: (((2 integer-address) (deref)) <- ((next-input))) arg: nil 0 (20 10) run: init-fake-terminal/main 3: 20 => ((2 integer-address) (deref)) mem: ((2 integer-address) (deref)): 1063 <= 20 run: init-fake-terminal/main 4: (((3 integer-address)) <- ((get-address)) ((1 terminal-address) (deref)) ((0 offset))) run: init-fake-terminal/main 4: 1062 => ((3 integer-address)) mem: ((3 integer-address)): 1035 <= 1062 run: init-fake-terminal/main 5: (((3 integer-address) (deref)) <- ((next-input))) arg: nil 1 (20 10) run: init-fake-terminal/main 5: 10 => ((3 integer-address) (deref)) mem: ((3 integer-address) (deref)): 1062 <= 10 run: init-fake-terminal/main 6: (((4 integer-address)) <- ((get-address)) ((1 terminal-address) (deref)) ((2 offset))) run: init-fake-terminal/main 6: 1064 => ((4 integer-address)) mem: ((4 integer-address)): 1036 <= 1064 run: init-fake-terminal/main 7: (((4 integer-address) (deref)) <- ((copy)) ((0 literal))) run: init-fake-terminal/main 7: 0 => ((4 integer-address) (deref)) mem: ((4 integer-address) (deref)): 1064 <= 0 run: init-fake-terminal/main 8: (((5 integer-address)) <- ((get-address)) ((1 terminal-address) (deref)) ((3 offset))) run: init-fake-terminal/main 8: 1065 => ((5 integer-address)) mem: ((5 integer-address)): 1037 <= 1065 run: init-fake-terminal/main 9: (((5 integer-address) (deref)) <- ((copy)) ((0 literal))) run: init-fake-terminal/main 9: 0 => ((5 integer-address) (deref)) mem: ((5 integer-address) (deref)): 1065 <= 0 run: init-fake-terminal/main 10: (((6 integer)) <- ((multiply)) ((2 integer-address) (deref)) ((3 integer-address) (deref))) mem: ((2 integer-address) (deref)) => 20 mem: ((3 integer-address) (deref)) => 10 run: init-fake-terminal/main 10: 200 => ((6 integer)) mem: ((6 integer)): 1038 <= 200 run: init-fake-terminal/main 11: (((7 string-address-address)) <- ((get-address)) ((1 terminal-address) (deref)) ((4 offset))) run: init-fake-terminal/main 11: 1066 => ((7 string-address-address)) mem: ((7 string-address-address)): 1039 <= 1066 run: init-fake-terminal/main 12: (((7 string-address-address) (deref)) <- ((new)) ((string literal)) ((6 integer))) mem: ((6 integer)) => 200 run: init-fake-terminal/main 12: 1067 => ((7 string-address-address) (deref)) mem: ((7 string-address-address) (deref)): 1066 <= 1067 run: init-fake-terminal/main 13: (((clear-screen)) ((1 terminal-address))) mem: ((1 terminal-address)) => 1062 run: clear-screen/init-fake-terminal/main 0: (((default-space space-address)) <- ((new)) ((space literal)) ((30 literal))) run: clear-screen/init-fake-terminal/main 0: 1268 => ((default-space space-address)) run: clear-screen/init-fake-terminal/main 1: (((1 terminal-address)) <- ((next-input))) arg: nil 0 (1062) run: clear-screen/init-fake-terminal/main 1: 1062 => ((1 terminal-address)) mem: ((1 terminal-address)): 1270 <= 1062 run: clear-screen/init-fake-terminal/main 2: (((jump-unless)) ((1 terminal-address)) ((10 offset))) mem: ((1 terminal-address)) => 1062 run: clear-screen/init-fake-terminal/main 3: (((2 string-address)) <- ((get)) ((1 terminal-address) (deref)) ((4 offset))) mem: ((1066 string-address) (raw)) => 1067 run: clear-screen/init-fake-terminal/main 3: 1067 => ((2 string-address)) mem: ((2 string-address)): 1271 <= 1067 run: clear-screen/init-fake-terminal/main 4: (((3 integer)) <- ((length)) ((2 string-address) (deref))) array-len: ((2 string-address) (deref)) mem: ((1067 integer) (raw)) => 200 run: clear-screen/init-fake-terminal/main 4: 200 => ((3 integer)) mem: ((3 integer)): 1272 <= 200 run: clear-screen/init-fake-terminal/main 5: (((4 integer)) <- ((copy)) ((0 literal))) run: clear-screen/init-fake-terminal/main 5: 0 => ((4 integer)) mem: ((4 integer)): 1273 <= 0 run: clear-screen/init-fake-terminal/main 6: (((5 boolean)) <- ((greater-or-equal)) ((4 integer)) ((3 integer))) mem: ((4 integer)) => 0 mem: ((3 integer)) => 200 run: clear-screen/init-fake-terminal/main 6: nil => ((5 boolean)) mem: ((5 boolean)): 1274 <= nil run: clear-screen/init-fake-terminal/main 7: (((jump-if)) ((5 boolean)) ((4 offset))) mem: ((5 boolean)) => nil run: clear-screen/init-fake-terminal/main 8: (((1 byte-address)) <- ((index-address)) ((2 string-address) (deref)) ((4 integer))) mem: ((4 integer)) => 0 array-len: ((1067 string) (raw)) mem: ((1067 integer) (raw)) => 200 run: clear-screen/init-fake-terminal/main 8: 1068 => ((1 byte-address)) mem: ((1 byte-address)): 1270 <= 1068 run: clear-screen/init-fake-terminal/main 9: (((1 byte-address) (deref)) <- ((copy)) (( literal))) run: clear-screen/init-fake-terminal/main 9: #\space => ((1 byte-address) (deref)) mem: ((1 byte-address) (deref)): 1068 <= #\space run: clear-screen/init-fake-terminal/main 10: (((4 integer)) <- ((add)) ((4 integer)) ((1 literal))) mem: ((4 integer)) => 0 run: clear-screen/init-fake-terminal/main 10: 1 => ((4 integer)) mem: ((4 integer)): 1273 <= 1 run: clear-screen/init-fake-terminal/main 11: (((jump)) ((-6 offset))) run: clear-screen/init-fake-terminal/main 6: (((5 boolean)) <- ((greater-or-equal)) ((4 integer)) ((3 integer))) mem: ((4 integer)) => 1 mem: ((3 integer)) => 200 run: clear-screen/init-fake-terminal/main 6: nil => ((5 boolean)) mem: ((5 boolean)): 1274 <= nil run: clear-screen/init-fake-terminal/main 7: (((jump-if)) ((5 boolean)) ((4 offset))) mem: ((5 boolean)) => nil run: clear-screen/init-fake-terminal/main 8: (((1 byte-address)) <- ((index-address)) ((2 string-address) (deref)) ((4 integer))) mem: ((4 integer)) => 1 array-len: ((1067 string) (raw)) mem: ((1067 integer) (raw)) => 200 run: clear-screen/init-fake-terminal/main 8: 1069 => ((1 byte-address)) mem: ((1 byte-address)): 1270 <= 1069 run: clear-screen/init-fake-terminal/main 9: (((1 byte-address) (deref)) <- ((copy)) (( literal))) run: clear-screen/init-fake-terminal/main 9: #\space => ((1 byte-address) (deref)) mem: ((1 byte-address) (deref)): 1069 <= #\space run: clear-screen/init-fake-terminal/main 10: (((4 integer)) <- ((add)) ((4 integer)) ((1 literal))) mem: ((4 integer)) => 1 run: clear-screen/init-fake-terminal/main 10: 2 => ((4 integer)) mem: ((4 integer)): 1273 <= 2 run: clear-screen/init-fake-terminal/main 11: (((jump)) ((-6 offset))) run: clear-screen/init-fake-terminal/main 6: (((5 boolean)) <- ((greater-or-equal)) ((4 integer)) ((3 integer))) mem: ((4 integer)) => 2 mem: ((3 integer)) => 200 run: clear-screen/init-fake-terminal/main 6: nil => ((5 boolean)) mem: ((5 boolean)): 1274 <= nil run: clear-screen/init-fake-terminal/main 7: (((jump-if)) ((5 boolean)) ((4 offset))) mem: ((5 boolean)) => nil run: clear-screen/init-fake-terminal/main 8: (((1 byte-address)) <- ((index-address)) ((2 string-address) (deref)) ((4 integer))) mem: ((4 integer)) => 2 array-len: ((1067 string) (raw)) mem: ((1067 integer) (raw)) => 200 run: clear-screen/init-fake-terminal/main 8: 1070 => ((1 byte-address)) mem: ((1 byte-address)): 1270 <= 1070 run: clear-screen/init-fake-terminal/main 9: (((1 byte-address) (deref)) <- ((copy)) (( literal))) run: clear-screen/init-fake-terminal/main 9: #\space => ((1 byte-address) (deref)) mem: ((1 byte-address) (deref)): 1070 <= #\space run: clear-screen/init-fake-terminal/main 10: (((4 integer)) <- ((add)) ((4 integer)) ((1 literal))) mem: ((4 integer)) => 2 run: clear-screen/init-fake-terminal/main 10: 3 => ((4 integer)) mem: ((4 integer)): 1273 <= 3 run: clear-screen/init-fake-terminal/main 11: (((jump)) ((-6 offset))) run: clear-screen/init-fake-terminal/main 6: (((5 boolean)) <- ((greater-or-equal)) ((4 integer)) ((3 integer))) mem: ((4 integer)) => 3 mem: ((3 integer)) => 200 run: clear-screen/init-fake-terminal/main 6: nil => ((5 boolean)) mem: ((5 boolean)): 1274 <= nil run: clear-screen/init-fake-terminal/main 7: (((jump-if)) ((5 boolean)) ((4 offset))) mem: ((5 boolean)) => nil run: clear-screen/init-fake-terminal/main 8: (((1 byte-address)) <- ((index-address)) ((2 string-address) (deref)) ((4 integer))) mem: ((4 integer)) => 3 array-len: ((1067 string) (raw)) mem: ((1067 integer) (raw)) => 200 run: clear-screen/init-fake-terminal/main 8: 1071 => ((1 byte-address)) mem: ((1 byte-address)): 1270 <= 1071 run: clear-screen/init-fake-terminal/main 9: (((1 byte-address) (deref)) <- ((copy)) (( literal))) run: clear-screen/init-fake-terminal/main 9: #\space => ((1 byte-address) (deref)) mem: ((1 byte-address) (deref)): 1071 <= #\space run: clear-screen
import strutils, os, osproc, strtabs, streams, sockets
const
wwwNL* = "\r\L"
ServerSig = "Server: httpserver.nim/1.0.0" & wwwNL
type
TRequestMethod = enum reqGet, reqPost
TServer* = object ## contains the current server state
s: Socket
job: seq[TJob]
TJob* = object
client: Socket
process: Process
# --------------- output messages --------------------------------------------
proc sendTextContentType(client: Socket) =
send(client, "Content-type: text/html" & wwwNL)
send(client, wwwNL)
proc badRequest(client: Socket) =
# Inform the client that a request it has made has a problem.
send(client, "HTTP/1.0 400 BAD REQUEST" & wwwNL)
sendTextContentType(client)
send(client, "<p>Your browser sent a bad request, " &
"such as a POST without a Content-Length.</p>" & wwwNL)
proc cannotExec(client: Socket) =
send(client, "HTTP/1.0 500 Internal Server Error" & wwwNL)
sendTextContentType(client)
send(client, "<P>Error prohibited CGI execution.</p>" & wwwNL)
proc headers(client: Socket, filename: string) =
# XXX could use filename to determine file type
send(client, "HTTP/1.0 200 OK" & wwwNL)
send(client, ServerSig)
sendTextContentType(client)
proc notFound(client: Socket, path: string) =
send(client, "HTTP/1.0 404 NOT FOUND" & wwwNL)
send(client, ServerSig)
sendTextContentType(client)
send(client, "<html><title>Not Found</title>" & wwwNL)
send(client, "<body><p>The server could not fulfill" & wwwNL)
send(client, "your request because the resource <b>" & path & "</b>" & wwwNL)
send(client, "is unavailable or nonexistent.</p>" & wwwNL)
send(client, "</body></html>" & wwwNL)
proc unimplemented(client: Socket) =
send(client, "HTTP/1.0 501 Method Not Implemented" & wwwNL)
send(client, ServerSig)
sendTextContentType(client)
send(client, "<html><head><title>Method Not Implemented" &
"</title></head>" &
"<body><p>HTTP request method not supported.</p>" &
"</body></HTML>" & wwwNL)
# ----------------- file serving ---------------------------------------------
proc discardHeaders(client: Socket) = skip(client)
proc serveFile(client: Socket, filename: string) =
discardHeaders(client)
var f: File
if open(f, filename):
headers(client, filename)
const bufSize = 8000 # != 8K might be good for memory manager
var buf = alloc(bufsize)
while true:
var bytesread = readBuffer(f, buf, bufsize)
if bytesread > 0:
var byteswritten = send(client, buf, bytesread)
if bytesread != bytesWritten:
let err = osLastError()
dealloc(buf)
close(f)
raiseOSError(err)
if bytesread != bufSize: break
dealloc(buf)
close(f)
client.close()
else:
notFound(client, filename)
# ------------------ CGI execution -------------------------------------------
proc executeCgi(server: var TServer, client: Socket, path, query: string,
meth: TRequestMethod) =
var env = newStringTable(modeCaseInsensitive)
var contentLength = -1
case meth
of reqGet:
discardHeaders(client)
env["REQUEST_METHOD"] = "GET"
env["QUERY_STRING"] = query
of reqPost:
var buf = ""
var dataAvail = true
while dataAvail:
dataAvail = recvLine(client, buf)
if buf.len == 0:
break
var L = toLower(buf)
if L.startsWith("content-length:"):
var i = len("content-length:")
while L[i] in Whitespace: inc(i)
contentLength = parseInt(substr(L, i))
if contentLength < 0:
badRequest(client)
return
env["REQUEST_METHOD"] = "POST"
env["CONTENT_LENGTH"] = $contentLength
send(client, "HTTP/1.0 200 OK" & wwwNL)
var process = startProcess(command=path, env=env)
var job: TJob
job.process = process
job.client = client
server.job.add(job)
if meth == reqPost:
# get from client and post to CGI program:
var buf = alloc(contentLength)
if recv(client, buf, contentLength) != contentLength:
let err = osLastError()
dealloc(buf)
raiseOSError(err)
var inp = process.inputStream
inp.writeData(buf, contentLength)
dealloc(buf)
proc animate(server: var TServer) =
# checks list of jobs, removes finished ones (pretty sloppy by seq copying)
var active_jobs: seq[TJob] = @[]
for i in 0..server.job.len-1:
var job = server.job[i]
if running(job.process):
active_jobs.add(job)
else:
# read process output stream and send it to client
var outp = job.process.outputStream
while true:
var line = outp.readstr(1024)
if line.len == 0:
break
else:
try:
send(job.client, line)
except:
echo("send failed, client diconnected")
close(job.client)
server.job = active_jobs
# --------------- Server Setup -----------------------------------------------
proc acceptRequest(server: var TServer, client: Socket) =
var cgi = false
var query = ""
var buf = ""
discard recvLine(client, buf)
var path = ""
var data = buf.split()
var meth = reqGet
var q = find(data[1], '?')
# extract path
if q >= 0:
# strip "?..." from path, this may be found in both POST and GET
path = data[1].substr(0, q-1)
else:
path = data[1]
# path starts with "/", by adding "." in front of it we serve files from cwd
path = "." & path
echo("accept: " & path)
if cmpIgnoreCase(data[0], "GET") == 0:
if q >= 0:
cgi = true
query = data[1].substr(q+1)
elif cmpIgnoreCase(data[0], "POST") == 0:
cgi = true
meth = reqPost
else:
unimplemented(client)
if path[path.len-1] == '/' or existsDir(path):
path = path / "index.html"
if not existsFile(path):
discardHeaders(client)
notFound(client, path)
client.close()
else:
when defined(Windows):
var ext = splitFile(path).ext.toLower
if ext == ".exe" or ext == ".cgi":
# XXX: extract interpreter information here?
cgi = true
else:
if {fpUserExec, fpGroupExec, fpOthersExec} * path.getFilePermissions != {}:
cgi = true
if not cgi:
serveFile(client, path)
else:
executeCgi(server, client, path, query, meth)
when isMainModule:
var port = 80
var server: TServer
server.job = @[]
server.s = socket(AF_INET)
if server.s == invalidSocket: raiseOSError(osLastError())
server.s.bindAddr(port=Port(port))
listen(server.s)
echo("server up on port " & $port)
while true:
# check for new new connection & handle it
var list: seq[Socket] = @[server.s]
if select(list, 10) > 0:
var client: Socket
new(client)
accept(server.s, client)
try:
acceptRequest(server, client)
except:
echo("failed to accept client request")
# pooling events
animate(server)
# some slack for CPU
sleep(10)
server.s.close()