#!/usr/bin/env rune
# Copyright 2004-2006 Kevin Reid, under the terms of the MIT X license
# found at http://www.opensource.org/licenses/mit-license.html ................
# This is a test application for an EIO implementation. It's a trivial web server.
pragma.enable("easy-return")
pragma.disable("explicit-result-guard")
pragma.enable("accumulator")
pragma.enable("dot-props")
def ALL := EIO::ALL
def FileNotFoundException :=
def network := (privilegedScope)
def makePipe :=
def makeInFilter :=
# Using dot-props everywhere as an experiment.
def [portStr, rootPathStr] := interp::args
def port := __makeInt(portStr, 10)
def root :=
# --- Utilities ---
def extensionToMediaType := {
def file :=
if (file.exists()) {
accum [].asMap() for rx`\s*(@mt[^#\s][^\s]*)\s+(@exts.*)\n` in file {
for ext ? (ext != "") in exts.split(" ") {
_.with (ext, mt)
}
}
} else {
["txt" => "text/plain", "html" => "text/html", "htm" => "text/html", "gif" => "image/gif", "xml" => "application/xml", "rdf" => "application/rdf+xml", "png" => "image/png", "svg" => "application/svg+xml", "xhtml" => "application/xhtml+xml"]
}
}
def ourCharset := "UTF-8"
def standardHeaders := `$\
Server: kEIO-Demo-Web-Server/0.0 kEIO/0.0
`
def asLines(charsIn) {
var lineBuffer := ""
def charsToLinesFilter(elements) {
def lines := (lineBuffer + __makeTwine.fromChars(elements, null)).split("\r\n")
stderr.print("lines now: ")
stderr.quote(lines)
stderr.println()
lineBuffer := lines.last()
return lines(0, lines.size() - 1)
}
def finishCharsToLines() {
return if (lineBuffer != "") { [lineBuffer] } else { [] }
}
return makeInFilter(charsIn, charsToLinesFilter,
"optFinishThunk" => finishCharsToLines,
"approxLengthFactor" => 1/50,
"filteredElementType" => String,
"likesAvailable" => 10,
"stderr" => stderr
)
}
# --- Making connection ---
def handler(ins, outs, originSpec, options) {
stderr.println(`handler($ins, $outs, $originSpec, $options)`)
def linesIn := asLines(ins)
def requestHeadersFlex := [].asMap().diverge(String, String)
def requestPath
def handleRequest
def pullHeaders
def sendProblem(p) {
stderr.println(`sendProblem($p)`)
outs.write(`HTTP/1.0 500 Internal Error$\r$\nContent-Type: text/plain$\r$\n$\r$\n$p${p.eStack()}`)
linesIn.close()
outs.close()
throw(p)
}
linesIn <- whenAvailable(1, fn{
stderr.println(`first line thunk`)
try {
def [`GET /@{bind requestPath} HTTP/@_`] := linesIn.read(1, 1)
pullHeaders()
} catch p {
sendProblem(p)
}
})
bind pullHeaders() {
stderr.println(`pullHeaders`)
try {
linesIn <- whenAvailable(1, fn{
escape stopHeaders {
for i => line in linesIn.peek(0, ALL) {
switch (line) {
match `` {
linesIn.skip(1)
handleRequest()
stopHeaders()
}
match `@key: @value` {
linesIn.skip(1)
# requestHeadersFlex.put(key, value, true)
# fixme: Work around E bug
requestHeadersFlex.put(key :String, value :String, true)
}
}
}
pullHeaders()
}
})
} catch p {
sendProblem(p)
}
}
bind handleRequest() {
stderr.println(`handleRequest`)
try {
linesIn.close()
# FIXME: we're assuming that there's unlimited buffer space - which is true with the current implementation but might not be in the future
try {
def file := if (requestPath == "") { root } else { root[requestPath] }
def [body, type] :=
if (file.isDirectory()) {
[file["index.html"].getText(), `text/html; charset="$ourCharset"`]
} else {
if (requestPath =~ rx`.*\.(@ext[^.]+)`) {
[file.getText(), extensionToMediaType.fetch(ext, fn{"application/octet-stream"})]
}
}
outs.write(`$\
HTTP/1.0 200 OK
$standardHeaders$\
Content-Length: ${body.size()}
Content-Type: text/plain; charset="$ourCharset"
$body`.replaceAll("\n", "\r\n"))
outs.close()
} catch p ? FileNotFoundException.accepts(p.leaf()) {
outs.write(`$\
HTTP/1.0 404 Not Found
$standardHeaders$\
Content-Type: text/plain; charset="$ourCharset"
Not found: $requestPath
` .replaceAll("\n", "\r\n"))
outs.close()
}
} catch p {
sendProblem(p)
}
}
}
when (network <- bindLocalTCPEndpoint(null, port, handler, "textStreams" => ourCharset, "reuseAddress" => true)) -> {
stderr.println("Ready")
} catch p {
interp.exitAtTop(p)
}
interp.blockAtTop()