-- An example HTTP server written in Konjac
module Rum.Request
struct [
method: String;
path: String;
headers: Map(String, String);
]
end
module Rum.Response
struct [
statusCode: Int64 = 200;
headers: Map(String, String) = {};
body: Binary = Binary { };
]
export function encode(response : Rum.Response) : Binary
reasonPhrase : String = case response.statusCode
when 200, do: "OK"
when 201, do: "Created"
when 301, do: "Moved Permanently"
when 400, do: "Bad Request"
when 404, do: "Not Found"
when 500, do: "Internal Server Error"
else do: "Unknown"
end
statusLine : String = "HTTP/1.1 " + Int64.toString(response.statusCode) + " " + reasonPhrase + "\r\n"
updatedHeaders : Map(String, String) = if Map.hasKey(response.headers, "Content-Length")
response.headers
else
Map.put(response.headers, "Content-Length", String.fromInt(Binary.length(response.body)))
end
headersString : String = updatedHeaders
|> Map.toList()
|> Array.map(function(pair : {String, String}) do
pair.key + ": " + pair.value + "\r\n"
end)
|> String.join()
|> String.concatenate("\r\n\r\n")
statusLine
|> String.concatenate(headersString)
|> Binary.fromString()
|> Binary.concatenate(response.body)
end
end
module Rum.Context
struct [
request: Rum.Request;
response: Rum.Response;
]
end
behaviour Rum.Handler
abstract function call(handler : Rum.Handler, context : Rum.Context) : Rum.Context
end
module Rum.Server
struct [
host: String;
port: UInt16;
handlers: Array(Rum.Handler);
]
function handleClient(server : Rum.Server, client : TCP.Client) : Nil
data : Binary = TCP.receive(client)
[headerSection, messageBody] =
data
|> Binary.toString()
|> String.split("\r\n\r\n")
[startLine, ...unprocessedHeaders]
headerSection
|> String.split("\r\n")
headers =
unprocessedHeaders
|> Array.map(function(header : String) do
[key, value] = String.split(header)
{key: value}
end)
|> Map.zip()
[method, path, "HTTP/1.1"] = startLine |> String.split(" ") |> String.stripSuffix()
context =
Rum.Context {
request: Rum.Request {
method: method,
path: path,
headers: headers
},
response: Rum.Response { }
}
updatedContext =
server.handlers
|> Array.reduce(function(handler : Rum.Handler, accumulator : Rum.Context) do
Rum.handler.call(handler, accumulator)
end)
encodedResponse = Rum.Response.encode(updatedContext.response)
TCP.send(client, encodedResponse)
end
export function bind(server : Rum.Server) : Rum.Response
loop do
client : TCP.Client? = TCP.accept(server.host, server.port)
if client do
Process.spawn do
handleClient(server, client)
end
else
Process.killSelf()
end
end
end
end
module Rum.Application
export function main(arguments : Array(String)) : Nil
server = Rum.Server { host: "127.0.0.1", port: 8080 }
supervisor =
Supervisor {
strategy: "OneForOne",
maximumRestarts: 5,
restartWindow: 10
}
process = Process.spawn do
Rum.Server.bind(server)
end
child = Child {
id: "worker",
process: process,
restart: "Transiet",
maxRestarts: 3,
restartWindow: 5
}
Supervisor.addChild(supervisor, child)
end
end