diff --git a/examples/http_server/cookie.noja b/examples/http_server/cookie.noja
new file mode 100644
index 0000000..183b05c
--- /dev/null
+++ b/examples/http_server/cookie.noja
@@ -0,0 +1,107 @@
+
+fun isSeparator(char: String)
+ return char == "(" or char == ")"
+ or char == "<" or char == ">"
+ or char == "@" or char == ","
+ or char == ";" or char == ":"
+ or char == "\\" or char == '"'
+ or char == "/" or char == "?"
+ or char == "[" or char == "]"
+ or char == "=" or char == " "
+ or char == "{" or char == "}"
+ or char == "\t";
+
+fun isControl(char: String) {
+ n = string.ord(char);
+ return (n >= 0 and n <= 31) or n == 127;
+}
+
+fun isChar(char: String)
+ return string.ord(char) < 128;
+
+fun isToken(char: String)
+ return isChar(char)
+ and not isControl(char)
+ and not isSeparator(char);
+
+fun parseToken(src: String, i: int) {
+ offset = i;
+ while i < count(src) and isToken(src[i]):
+ i = i+1;
+ length = i - offset;
+ return string.slice(src, offset, length), i;
+}
+
+fun isCookieOctet(char: String) {
+ n = string.ord(char);
+ return n == 33
+ or (n >= 35 and n <= 43)
+ or (n >= 45 and n <= 58)
+ or (n >= 60 and n <= 91)
+ or (n >= 93 and n <= 126);
+}
+
+fun parseCookieValue(src: String, i: int) {
+
+ dquote = false;
+ if i < count(src) and src[i] == '"': {
+ dquote = true;
+ i = i+1;
+ }
+
+ if i == count(src) or not isCookieOctet(src[i]):
+ return none, none, "Missing value";
+
+ offset = i;
+ do
+ i = i+1;
+ while i < count(src) and isCookieOctet(src[i]);
+ length = i - offset;
+
+ if dquote: {
+ if i == count(src) or src[i] != '"':
+ return none, none, "Missing closing double quote";
+ i = i+1;
+ }
+
+ return string.slice(src, offset, length), i;
+}
+
+fun parseCookiePair(src: String, i: int) {
+
+ while i < count(src) and src[i] == " ":
+ i = i+1;
+
+ name, i = parseToken(src, i);
+
+ if i == count(src) or src[i] != "=":
+ return none, none, none, "Missing \"=\" after name";
+ i = i+1; # Skip the "="
+
+ value, i, error = parseCookieValue(src, i);
+ if error != none:
+ return none, none, none, error;
+
+ while i < count(src) and src[i] == " ":
+ i = i+1;
+
+ if i < count(src) and src[i] == ";":
+ i = i+1;
+
+ return name, value, i;
+}
+
+return {
+
+ fun parseCookieHeader(src: String) {
+ cookies = {};
+ i = 0;
+ while i < count(src): {
+ name, value, i, error = parseCookiePair(src, i);
+ if error != none:
+ return none, error;
+ cookies[name] = value;
+ }
+ return cookies;
+ }
+};
\ No newline at end of file
diff --git a/examples/http_server/main.noja b/examples/http_server/main.noja
index 69c0f56..450c77f 100755
--- a/examples/http_server/main.noja
+++ b/examples/http_server/main.noja
@@ -1,10 +1,39 @@
-server = import("server.noja");
-request = import("request.noja");
-response = import("response.noja");
+server = import("server.noja");
+request = import("request.noja");
+response = import("response.noja");
+cookie = import("cookie.noja");
+urlencoded = import("urlencoded.noja");
Request = request.Request;
+respond = response.new;
+
+fun getCookie(req: Request, name: String) {
+ src = req.headers["Cookie"];
+ if src == none:
+ return none;
+ cookies, error = cookie.parseCookieHeader(src);
+ if error != none:
+ return none, error;
+ return cookies[name];
+}
+
+fun getSessionID(req: Request) {
+ session_id, error = getCookie(req, "SESSID");
+ if error != none:
+ return none, error;
+ session_id = string.toInt(session_id);
+ return session_id;
+}
+
+fun getUsername(req: Request) {
+ session_id, error = getSessionID(req);
+ if error != none:
+ return none, none, error;
+ username = session_table[session_id];
+ return username, session_id;
+}
fun loadFile(path: String) {
-
+
stream, error = files.openFile(path, files.READ);
if error != none:
return none, error;
@@ -23,77 +52,209 @@ fun loadFile(path: String) {
} while num_bytes == size;
- files.close(stream);
+ #files.close(stream);
return text;
}
-fun generateHome() {
- title = "Homepage";
- content = "Hello, world!";
- return string.cat(
- "",
- "
",
- "", title, "",
- "",
- "",
- "", content, "",
- "",
- ""
- );
+user_table = {};
+session_table = {};
+
+fun redirect(endpoint) {
+ fun callback()
+ return response.new(307, none, {'Location': endpoint});
+ return callback;
}
-fun generateLogout() {
-}
+route_table = {
+ '/': {
+ GET: redirect("/login")
+ },
+ '/home': {
+ fun GET(req: Request) {
+
+ username, _, error = getUsername(req);
+ if error != none:
+ return respond(400, error);
+ if username == none:
+ return respond(401, "You're not logged in");
+
+ content = string.cat(
+ "",
+ "",
+ "Homepage",
+ "",
+ "",
+ "Hello, ", username, "!",
+ "",
+ ""
+ );
+ return respond(200, content);
+ }
+ },
+ '/login': {
+ GET: 'pages/login.html',
+ fun POST(req: Request) {
-table = {
- '/': 'login.html',
- '/home': generateHome,
- '/login': 'login.html',
- '/signup': 'signup.html',
- '/logout': generateLogout
+ username, _, error = getUsername(req);
+ if error != none:
+ return respond(400, error);
+ if username != none:
+ return respond(400, "You're already logged in");
+
+ fun areValid(params) {
+ expect = {user: String, pass: String};
+ return istypeof(expect, params)
+ and count(params.user) > 0
+ and count(params.pass) > 0;
+ }
+
+ params = urlencoded.parse(req.body);
+ if not areValid(params):
+ return respond(400, "Invalid parameters");
+ username = params.user;
+ password = params.pass;
+
+ info = user_table[username];
+
+ if info == none:
+ return respond(200, "No such user");
+ if info.pass != password:
+ return respond(200, "Wrong password");
+
+ # Logged in!
+ session_id = generateRandomValue(0, 1000);
+ session_table[session_id] = username;
+
+ message = cat("Welcome, ", username, "!");
+ headers = {
+ "Location" : "/home",
+ "Set-Cookie": cat("SESSID=", session_id)
+ };
+ return respond(303, message, headers);
+ }
+ },
+ '/signup': {
+
+ GET: 'pages/signup.html',
+
+ fun POST(req: Request) {
+
+ username, _, error = getUsername(req);
+ if error != none:
+ return respond(400, error);
+ if username != none:
+ return respond(400, "You're already logged in");
+
+ fun areValid(params) {
+ expect = {user: String, pass: String};
+ return istypeof(expect, params)
+ and count(params.user) > 0
+ and count(params.pass) > 0;
+ }
+
+ params = urlencoded.parse(req.body);
+ if not areValid(params):
+ return respond(400, "Invalid parameters");
+ username = params.user;
+ password = params.pass;
+
+ if user_table[password] != none:
+ return respond(200, "User already exists");
+
+ # Signed up!
+ user_table[username] = {pass: password};
+ session_id = random.generate(0, 1000);
+ session_table[session_id] = username;
+
+ # Respond
+ message = string.cat("Welcome, ", username, "!");
+ headers = {
+ "Location" : "/home",
+ "Set-Cookie": string.cat("SESSID=", toString(session_id))
+ };
+ return respond(303, message, headers);
+ }
+ },
+ '/logout': {
+ fun GET(req: Request) {
+
+ username, session_id, error = getUsername(req);
+ if error != none:
+ return respond(400, error);
+ if username == none:
+ return respond(401, "You're not logged in");
+
+ session_table[session_id] = none;
+
+ return respond(303, "Goodbye!", {"Location": "/login"});
+ }
+ }
};
-default = 'notfound.html';
+fun join(list: List, glue="") {
+ if count(list) == 0:
+ text = "";
+ else {
+ i = 1;
+ text = toString(list[0]);
+ while i < count(list): {
+ text = string.cat(text, glue, toString(list[i]));
+ i = i+1;
+ }
+ }
+ return text;
+}
+
debug = true;
+fun joinPath(a, b) {
+ u = a[count(a)-1] == '/';
+ v = b[count(b)-1] == '/';
+ if u and v:
+ return string.cat(string.slice(a, 0, count(a)-1), b);
+ if u or v:
+ return string.cat(a, b);
+ return string.cat(a, '/', b);
+}
+
+fun getRouteAllowedMethods(route) {
+
+ method_table = route_table[route];
+ if method_table == none:
+ return none;
+
+ allowed = keysof(method_table);
+ if method_table.HEAD == none and method_table.GET != none:
+ append(allowed, "HEAD");
+ if method_table.OPTIONS == none:
+ append(allowed, "OPTIONS");
+ return allowed;
+}
+
fun callback(req: Request) {
- path = req.url.path;
+ endpoint = req.url.path;
+ method_table = route_table[endpoint];
+ if method_table == none:
+ return response.new(404);
- if path == "/" or path == "/login": {
-
- if req.method == "POST": {
-
-
-
- } else if req.method == "GET"; {
-
- } else {
- assert(false);
- }
- }
-
- # Generate response body
- {
- entry = table[req.url.path];
- if entry == none: {
- entry = default;
- not_found = true;
- } else
- not_found = false;
-
- if isCallable(entry):
- data, error = entry(req);
+ method_item = method_table[req.method];
+ if method_item == none: {
+ allowed = getRouteAllowedMethods(endpoint);
+ if req.method == "OPTIONS":
+ status = 204;
else
- data, error = loadFile(entry);
+ status = 405;
+ return response.new(status, none, {"Allow": join(allowed, ", ")});
}
- # Generate response object
- {
- if error == none:
+ if isCallable(method_item):
+ res = method_item(req);
+ else {
+ file = joinPath(getCurrentWorkingDirectory(), method_item);
+ data, error = loadFile(file);
+ if error == none:
res = response.new(200, data);
- else if not_found:
- res = response.new(404, data);
else if debug:
res = response.new(500, error);
else
diff --git a/examples/http_server/pages/login.html b/examples/http_server/pages/login.html
index bfc32f4..a211aed 100644
--- a/examples/http_server/pages/login.html
+++ b/examples/http_server/pages/login.html
@@ -1,12 +1,12 @@
- Login page
+ Log-in page
-
\ No newline at end of file
+