#!/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()