import "net" for Server, Socket
import "io" for File, Directory, Stat
import "scheduler" for Scheduler
class HttpServer {
construct new(port) {
_port = port
_server = Server.bind("0.0.0.0", port)
System.print("HTTP Server listening on http://localhost:%(port)")
}
run() {
while (true) {
var socket = _server.accept()
Fiber.new {
handleClient_(socket)
}.call()
}
}
handleClient_(socket) {
var requestData = socket.read()
if (requestData == null || requestData == "") {
socket.close()
return
}
var lines = requestData.split("\r\n")
if (lines.count == 0) {
socket.close()
return
}
var requestLine = lines[0].split(" ")
if (requestLine.count < 2) {
sendError_(socket, 400, "Bad Request")
return
}
var method = requestLine[0]
var path = requestLine[1]
System.print("%(method) %(path)")
if (method != "GET") {
sendError_(socket, 405, "Method Not Allowed")
return
}
path = path.replace("\%20", " ")
if (path.contains("..")) {
sendError_(socket, 403, "Forbidden")
return
}
var localPath = "." + path
if (localPath.endsWith("/")) localPath = localPath[0..-2]
if (localPath == "") localPath = "."
if (!exists(localPath)) {
sendError_(socket, 404, "Not Found")
return
}
if (isDirectory(localPath)) {
serveDirectory_(socket, localPath, path)
} else {
serveFile_(socket, localPath)
}
socket.close()
}
exists(path) {
if (File.exists(path)) return true
if (Directory.exists(path)) return true
return false
}
isDirectory(path) {
return Directory.exists(path)
}
serveDirectory_(socket, localPath, requestPath) {
var files
var fiber = Fiber.new {
files = Directory.list(localPath)
}
fiber.try()
if (fiber.error != null) {
sendError_(socket, 500, "Internal Server Error: " + fiber.error)
return
}
files.sort(Fn.new {|a, b|
var ba = a.bytes
var bb = b.bytes
var len = ba.count
if (bb.count < len) len = bb.count
for (i in 0...len) {
if (ba[i] < bb[i]) return true
if (ba[i] > bb[i]) return false
}
return ba.count < bb.count
})
var html = "<!DOCTYPE html><html><head><title>Index of %(requestPath)</title></head><body>"
html = html + "<h1>Index of %(requestPath)</h1><hr><ul>"
if (requestPath != "/") {
var parent = requestPath.split("/")
if (parent.count > 1) {
parent.removeAt(-1)
var parentPath = parent.join("/")
if (parentPath == "") parentPath = "/"
html = html + "<li><a href=\"%(parentPath)\">..</a></li>"
}
}
for (file in files) {
var href = requestPath
if (!href.endsWith("/")) href = href + "/"
href = href + file
html = html + "<li><a href=\"%(href)\">%(file)</a></li>"
}
html = html + "</ul><hr></body></html>"
sendResponse_(socket, 200, "OK", "text/html", html)
}
serveFile_(socket, localPath) {
var content
var fiber = Fiber.new {
content = File.read(localPath)
}
fiber.try()
if (fiber.error != null) {
sendError_(socket, 500, "Error reading file: " + fiber.error)
return
}
var contentType = "application/octet-stream"
if (localPath.endsWith(".html")) {
contentType = "text/html"
} else if (localPath.endsWith(".txt")) {
contentType = "text/plain"
} else if (localPath.endsWith(".wren")) {
contentType = "text/plain"
} else if (localPath.endsWith(".c")) {
contentType = "text/plain"
} else if (localPath.endsWith(".h")) {
contentType = "text/plain"
} else if (localPath.endsWith(".md")) {
contentType = "text/markdown"
} else if (localPath.endsWith(".json")) {
contentType = "application/json"
} else if (localPath.endsWith(".png")) {
contentType = "image/png"
} else if (localPath.endsWith(".jpg")) {
contentType = "image/jpeg"
}
sendResponse_(socket, 200, "OK", contentType, content)
}
sendError_(socket, code, message) {
var html = "<h1>%(code) %(message)</h1>"
sendResponse_(socket, code, message, "text/html", html)
socket.close()
}
sendResponse_(socket, code, status, contentType, body) {
var response = "HTTP/1.1 %(code) %(status)\r\n" +
"Content-Type: %(contentType)\r\n" +
"Content-Length: %(body.count)\r\n" +
"Connection: close\r\n" +
"\r\n" +
body
socket.write(response)
}
}
var server = HttpServer.new(8080)
server.run()