#!/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 asLines :=
def int8 :=
def network := (privilegedScope)
def bufferToByteList :=
def makePipe :=
def makeHTTPWriter :=
def makeInCharDecoder :=
def makeInFilter :=
def makeJInputWrapper :=
def makeListStream :=
def makeOutCharEncoder :=
# 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 ourCharsetName := "UTF-8"
def ourCharset := .forName(ourCharsetName)
def encode(s) {
return bufferToByteList(ourCharset.encode(s))
}
def standardHeaders := `$\
Server: kEIO-Demo-Web-Server/0.0 kEIO/0.0
`
# --- Making connection ---
def handler(ins, outs, originSpec, options) {
stderr.println(`handler($ins, $outs, $originSpec, $options)`)
def linesIn := asLines(makeInCharDecoder(ins, ourCharsetName), "\r\n")
def connWriter := makeHTTPWriter(outs, => stderr)
def responseWriter := connWriter.next()
def requestHeadersFlex := [].asMap().diverge(String, String)
def requestPath
def handleRequest
def pullHeaders
def sendProblem(p) {
stderr.println(`sendProblem($p)${p.eStack()}`)
throw <- (p)
try {
responseWriter.response("1.0", 500, "Internal Error")
} catch _ {}
responseWriter.optionalHeader("Content-Type", `text/plain; charset="$ourCharsetName"`)
responseWriter.body(makeListStream(encode(`$p${p.eStack()}`), int8))
responseWriter.finish()
linesIn.close()
throw(p)
}
linesIn <- whenAvailable(1, fn{
stderr.println(`first line thunk`)
if (linesIn.isTerminated()) {
return
}
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()
try {
def file := if (requestPath == "") { root } else { root[requestPath] }
def [jstream, type] :=
if (file.isDirectory()) {
[file["index.html"].toURL().openStream(), `text/html; charset="$ourCharsetName"`]
} else {
def ext := if (requestPath =~ rx`.*\.(@ext[^.]+)`) { ext } else { null }
[file.toURL().openStream(), extensionToMediaType.fetch(ext, fn{"application/octet-stream"})]
}
responseWriter.response("1.0", 200, "OK")
responseWriter.header("Content-Type", type)
responseWriter.body(makeJInputWrapper(jstream, , => stderr))
responseWriter.finish()
} catch p ? FileNotFoundException.accepts(p.leaf()) {
responseWriter.response("1.0", 404, "Not Found")
responseWriter.header("Content-Type", `text/plain; charset="$ourCharsetName"`)
responseWriter.body(makeListStream(encode(`$\
Not found: $requestPath$\r
`), int8))
responseWriter.finish()
}
} catch p {
sendProblem(p)
}
}
}
when (network <- bindLocalTCPEndpoint(null, port, handler, "reuseAddress" => true)) -> {
stderr.println("Ready")
} catch p {
interp.exitAtTop(p)
}
interp.blockAtTop()