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("