WebServer := Server clone do ( Location := Object clone // set this to an open File instance if you want access.log to go to a file // otherwise, it will go to stdout logFile := Nil makeLocation := method(request, site, parent, path, segments, resource, Location clone do ( // the request object which is being rendered. request := request // the site object. site := site // the parent location object, creating a chain until parent is Nil parent := parent // The path of the current location object as a list of strings // This indicates which path segments were consumed to arrive at // the current location object path := path // The remaining segments as a list of strings. This indicates which // path segments have not yet been consumed. segments := segments // The Resource instance associated with the current Location. resource := resource ) ) handleSegments := method(resource, handled, remaining, // Resource findChild returns a handleSegments (because IO can't // return tuples?) to indicate the child to which it is now delegating // url traversal, the segments which it was asked to handle which it // actually did handle, and the segments which are still remaining // to be handled. Object clone do ( resource := resource handled := handled remaining := remaining ) ) Http := Object clone do ( talk := method(sock, serv, self socket := sock self server := serv self outgoingHeaders := Map clone self contentLength := 0 self headersSent := Nil // We assume the response is ok unless told otherwise setResponse(200, "OK") setHeader("Content-type", "text/html") // We can't do keepalives yet setHeader("Connection", "close") // Set the server and the date setHeader("Date", Date now asString("%a, %d %b %Y %H:%M:%S %Z")) setHeader("Server", "ioserv/0.1") while ((self newlineIndex := sock readBuffer asString findSeq("\n")) isNil, sock read ) parts := sock readBuffer asString slice(0, newlineIndex) split(" ") self command := parts at(0) self path := parts at(1) if (queryIndex := path findSeq("?"), self query := CGI parseString(path slice(queryIndex)) ) if (parts size == 2, self version := "HTTP/0.9" headerMap := Map clone headerIndex := newlineIndex , self version := parts at(2) readHeaders(sock) ) self actualCommand := self command if (headerMap hasKey("http-command-equiv"), self command = headerMap at("http-command-equiv") ) contentLength := headerMap at("content-length") if (contentLength isNil, contentLength = 0) if (leftInBuffer := sock readBuffer asString size - headerIndex < contentLength, sock read(contentLength - leftInBuffer) ) self body := sock readBuffer asString slice(headerIndex, sock readBuffer asString size) if (body asMutable strip size != 0, self arguments := CGI parseString(body asString asMutable lstrip("\r\n")) , self arguments := Map clone ) serv dispatchRequest(self) self ) readHeaders := method(sock, while ((self headerIndex := sock readBuffer asString findSeq("\r\n\r\n")) isNil, sock read ) self headers := sock readBuffer asString slice(newlineIndex, headerIndex) self headerMap := Map clone headerContinuation := "" headers split("\r\n") foreach(value, headerContinuation = headerContinuation .. value if(value at(0) != " " and value at(0) != "\t", splitHeader := headerContinuation split(":") headerMap atPut(splitHeader at(0) asLowercase, ":" join(splitHeader slice(1)) lstrip) headerContinuation = "" ) ) ) dump := method( self command linePrint self path linePrint self headers linePrint self body linePrint self headerMap keys print "" linePrint ) setResponse := method(code, message, self responseCode := code self responseMessage := message ) setHeader := method(key, value, outgoingHeaders atPut(key, value) ) sendList := method(data, if (headersSent isNil, self socket write( "HTTP/1.1 ", responseCode asString, " ", responseMessage, "\r\n") outgoingHeaders foreach(k, v, if (k isNil or v isNil, continue) self socket write(k, ": ", v, "\r\n") ) self socket write("\r\n") contentLength = 0 headersSent = 1 ) data foreach(v, self contentLength = self contentLength + v size self socket write(v)) ) send := method( sendList(thisMessage argsEvaluatedIn(sender)) ) ) // end http protocol implementation handleSocket := method(sock, @@spawnProtocol(sock) ) spawnProtocol := method(sock, startTime := Date clock protocol := Http clone talk(sock, self) runLength := Date clock - startTime asNumber // client ip, remote logname (from identd) (-), user name (-), // dateTime, method, uri, clientproto, response code, sent content length, referer, user agent logline := socket host .. " - - [" .. Date now asString("%d/%b/%Y:%H:%M:%S %Z") logline = logline .. "] \"" .. protocol command .. " " .. protocol path .. "\" " .. protocol responseCode logline = logline .. " " .. protocol contentLength .. " \"" .. (protocol headerMap at("referer") or "-") logline = logline .. "\" \"" .. (protocol headerMap at("user-agent") or "-") .. "\"" if (logFile, logFile write(logline), write(logline)) sock close ) dispatchRequest := method(request, consumed := List clone location := makeLocation(request, self, Nil, List clone, request path split("/"), root) while(location resource != Nil and location segments size > 0, handled := location resource findChild(location) consumed = consumed with(handled handled) location = makeLocation(request, self, location, consumed, handled remaining, handled resource) ) if (location resource isNil, return handleNotFound(location)) resource := location resource willHandle(location) if (resource != location resource, location = makeLocation(request, self, location, location path, List clone, resource)) location resource handle(location) ) handleNotFound := method(location, location request setResponse(404, "NOT FOUND") location request send("

Not Found

404 Not Found") ) listen := method(port, root, write("WebServer listening on port ", port, "\n") serv := self clone serv setPort(port) serv root := root serv ) ) Resource := Object clone do ( findChild := method(location, // Override findChild to handle multiple segments at a time. // Return a WebServer handleSegments, passing the Resource // object to delegate to, the segments which this call to findChild // handled in order to arrive at the new resource, and the segments // which remain to be handled. // Provide a named child_* method or a childFactory method to // handle segments using a simpler API. childMethod := self getSlot("child_" .. location segments at(0)) if (childMethod != Nil, child := childMethod(location) , child := childFactory(location, location segments at(0)) ) return WebServer handleSegments( child, location segments at(0), location segments slice(1)) ) child_ := method(location, // a url with a / at the end of it self ) childFactory := method(location, childName, // we handle everything below us as 404 by default Nil ) willHandle := method(location, self ) handle := method(location, // we render our template location request setHeader("Content-type", contentType) location request send(template) ) template := "Hello, World!" contentType := "text/html" ) True := Object clone False := Object clone JSON := Object clone do ( DEBUG := Nil to := method(object, if (object hasProto(Map), rv := "{" object foreach(k, v, rv = rv .. "\"" .. k asString asMutable replaceSeq("\"", "\\\"") .. "\"" rv = rv .. ":" rv = rv .. self to(v) rv = rv .. ",") rv = rv slice(0, -1) rv = rv .. "}" return rv) if (object hasProto(List), rv := "[" object foreach(child, rv = rv .. self to(child) rv = rv .. ",") if (object size != 0, rv = rv slice(0, -1)) rv = rv .. "]" return rv) if (object hasProto(Number), return object asString) if (object == True, return "true") if (object == False, return "false") if (object isNil, return "null") return "\"" .. object asString asMutable replaceSeq("\"", "\\\"") .. "\"" ) from := method(string, result := fromInternal(string) result value ) fromDelimited := method(string, beginDelimiter, endDelimiter, constructor, result, if(DEBUG, writeln("delimited", beginDelimiter, endDelimiter)) pos := 0 found := Nil nested := 1 while(found == Nil, pos = pos + 1 if (string at(pos) == "\"" at(0)) then ( pos = pos + findStringEnd(string slice(pos)) ) elseif (string at(pos) == beginDelimiter at(0), nested = nested + 1 ) elseif (string at(pos) == endDelimiter at(0), nested = nested - 1 if (nested == 0, if(DEBUG, writeln("calling constructor")) result value := constructor(string slice(1, pos)) result position := pos + 1 if(DEBUG, write("value ", result value, "\n")) return result ) ) ) ) fromInternal := method(string, result := Object clone if (string at(0) == "[" at(0)) then ( return fromDelimited(string, "[", "]", self getSlot("fromList"), result) ) elseif (string at(0) == "{" at (0), return fromDelimited(string, "{", "}", self getSlot("fromDict"), result) ) elseif (string at(0) == "\"" at(0), result position := findStringEnd(string) + 1 result value := string slice(1, result position - 1) return result ) else ( result position := 0 while(result position < string size and "1234567890." containsSeq(string slice(result position, result position+1)), result position = result position + 1 ) if(DEBUG, writeln("num ", result position, " ", string)) result value := string slice(0, result position) asNumber return result ) ) fromList := method(string, /* string is a string of the list contents, with the leading [ and the trailing ] omitted. */ if(DEBUG, writeln("fromList ", string)) me := List clone pos := 0 while (pos < string size, child := fromInternal(string slice(pos)) me append(child value) pos = pos + child position // take into account the comma and spaces while(pos < string size and ", \t\r\n" containsSeq(string slice(pos, pos + 1)), pos = pos + 1) if(DEBUG, write(string slice(pos, pos))) ) me ) fromDict := method(string, if(DEBUG, writeln("fromDict ", string)) me := Map clone pos := 0 while (pos < string size, key := fromInternal(string slice(pos)) pos = pos + key position while(pos < string size and ": \t\r\n" containsSeq(string slice(pos, pos + 1)), pos = pos + 1) value := fromInternal(string slice(pos)) pos = pos + value position while(pos < string size and ", \t\r\n" containsSeq(string slice(pos, pos + 1)), pos = pos + 1) if(DEBUG, writeln("atPut ", key value, " ", value value)) me atPut(key value asString, value value) ) me ) findStringEnd := method(string, if(DEBUG, writeln("findStringEnd ", string)) pos := 0 found := Nil backslash := Nil while (found == Nil, pos = pos + 1 if (backslash == Nil) then ( if (string at(pos) == "\\" at(0)) then ( backslash = 1 ) elseif (string at(pos) == "\"" at(0), found = 1 ) ) else ( backslash = Nil ) ) pos ) ) /* writeln(JSON from("1234.52")) writeln(JSON from("\"asdf\"")) writeln(JSON from("[1,2,[1,2,3]]")) writeln(JSON from("[\"as\",\"df\"]")) writeln(JSON from("{1:1,2:2}") keys) */ //doFile("/Users/dp/Debugger.io") //Debugger debug("JSON from(\"[1,2]\")")