advanced the web server example
This commit is contained in:
+119
-216
@@ -1,10 +1,9 @@
|
||||
server = import("server.noja");
|
||||
request = import("request.noja");
|
||||
response = import("response.noja");
|
||||
cookie = import("cookie.noja");
|
||||
newRouter = import("router.noja").new;
|
||||
serve = import("server.noja").serve;
|
||||
cookie = import("cookie.noja");
|
||||
Request = import("request.noja").Request;
|
||||
respond = import("response.noja").new;
|
||||
urlencoded = import("urlencoded.noja");
|
||||
Request = request.Request;
|
||||
respond = response.new;
|
||||
|
||||
fun getCookie(req: Request, name: String) {
|
||||
src = req.headers["Cookie"];
|
||||
@@ -32,239 +31,143 @@ fun getUsername(req: Request) {
|
||||
return username, session_id;
|
||||
}
|
||||
|
||||
fun loadFile(path: String) {
|
||||
|
||||
stream, error = files.openFile(path, files.READ);
|
||||
if error != none:
|
||||
return none, error;
|
||||
|
||||
text = "";
|
||||
size = 512;
|
||||
do {
|
||||
size = 2 * size;
|
||||
data = buffer.new(size);
|
||||
|
||||
num_bytes, error = files.read(stream, data);
|
||||
if error != none:
|
||||
return none, error;
|
||||
|
||||
text = string.cat(text, buffer.toString(data));
|
||||
|
||||
} while num_bytes == size;
|
||||
|
||||
#files.close(stream);
|
||||
return text;
|
||||
fun redirect(endpoint) {
|
||||
fun callback()
|
||||
return respond(307, none, {'Location': endpoint});
|
||||
return callback;
|
||||
}
|
||||
|
||||
user_table = {};
|
||||
session_table = {};
|
||||
|
||||
fun redirect(endpoint) {
|
||||
fun callback()
|
||||
return response.new(307, none, {'Location': endpoint});
|
||||
return callback;
|
||||
}
|
||||
router = newRouter();
|
||||
|
||||
route_table = {
|
||||
'/': {
|
||||
GET: redirect("/login")
|
||||
},
|
||||
'/home': {
|
||||
fun GET(req: Request) {
|
||||
router->plug("/", {GET: redirect("/login")});
|
||||
|
||||
username, _, error = getUsername(req);
|
||||
if error != none:
|
||||
return respond(400, error);
|
||||
if username == none:
|
||||
return respond(401, "You're not logged in");
|
||||
router->plug("/home", {
|
||||
|
||||
content = string.cat(
|
||||
"<html>",
|
||||
"<head>",
|
||||
"<title>Homepage</title>",
|
||||
"</head>",
|
||||
"<body>",
|
||||
"<a>Hello, ", username, "!</a>",
|
||||
"</body>",
|
||||
"</html>"
|
||||
);
|
||||
return respond(200, content);
|
||||
}
|
||||
},
|
||||
'/login': {
|
||||
GET: 'pages/login.html',
|
||||
fun POST(req: Request) {
|
||||
fun GET(req: Request) {
|
||||
|
||||
username, _, error = getUsername(req);
|
||||
if error != none:
|
||||
return respond(400, error);
|
||||
if username != none:
|
||||
return respond(400, "You're already logged in");
|
||||
username, _, error = getUsername(req);
|
||||
if error != none:
|
||||
return respond(400, error);
|
||||
if username == none:
|
||||
return respond(401, "You're not 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"});
|
||||
}
|
||||
content = string.cat(
|
||||
"<html>",
|
||||
"<head>",
|
||||
"<title>Homepage</title>",
|
||||
"</head>",
|
||||
"<body>",
|
||||
"<a>Hello, ", username, "!</a>",
|
||||
"</body>",
|
||||
"</html>"
|
||||
);
|
||||
return respond(200, content);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
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;
|
||||
router->plug("/login", {
|
||||
GET: 'pages/login.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;
|
||||
|
||||
info = user_table[username];
|
||||
|
||||
if info == none:
|
||||
return respond(200, "No such user");
|
||||
if info.pass != password:
|
||||
return respond(200, "Wrong password");
|
||||
|
||||
# Log-in succeded
|
||||
|
||||
# Create a new session
|
||||
session_id = random.generate(0, 1000);
|
||||
session_table[session_id] = username;
|
||||
|
||||
message = string.cat("Welcome, ", username, "!");
|
||||
headers = {
|
||||
"Location" : "/home",
|
||||
"Set-Cookie": string.cat("SESSID=", toString(session_id))
|
||||
};
|
||||
return respond(303, message, headers);
|
||||
}
|
||||
return text;
|
||||
}
|
||||
});
|
||||
|
||||
debug = true;
|
||||
router->plug("/signup", {
|
||||
GET: 'pages/signup.html',
|
||||
|
||||
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 POST(req: Request) {
|
||||
|
||||
fun getRouteAllowedMethods(route) {
|
||||
username, _, error = getUsername(req);
|
||||
if error != none:
|
||||
return respond(400, error);
|
||||
if username != none:
|
||||
return respond(400, "You're already logged in");
|
||||
|
||||
method_table = route_table[route];
|
||||
if method_table == none:
|
||||
return none;
|
||||
fun areValid(params) {
|
||||
expect = {user: String, pass: String};
|
||||
return istypeof(expect, params)
|
||||
and count(params.user) > 0
|
||||
and count(params.pass) > 0;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
params = urlencoded.parse(req.body);
|
||||
if not areValid(params):
|
||||
return respond(400, "Invalid parameters");
|
||||
username = params.user;
|
||||
password = params.pass;
|
||||
|
||||
fun callback(req: Request) {
|
||||
if user_table[password] != none:
|
||||
return respond(200, "User already exists");
|
||||
|
||||
endpoint = req.url.path;
|
||||
method_table = route_table[endpoint];
|
||||
if method_table == none:
|
||||
return response.new(404);
|
||||
# Signed up!
|
||||
user_table[username] = {pass: password};
|
||||
session_id = random.generate(0, 1000);
|
||||
session_table[session_id] = username;
|
||||
|
||||
method_item = method_table[req.method];
|
||||
if method_item == none: {
|
||||
allowed = getRouteAllowedMethods(endpoint);
|
||||
if req.method == "OPTIONS":
|
||||
status = 204;
|
||||
else
|
||||
status = 405;
|
||||
return response.new(status, none, {"Allow": join(allowed, ", ")});
|
||||
# Respond
|
||||
message = string.cat("Welcome, ", username, "!");
|
||||
headers = {
|
||||
"Location" : "/home",
|
||||
"Set-Cookie": string.cat("SESSID=", toString(session_id))
|
||||
};
|
||||
return respond(303, message, headers);
|
||||
}
|
||||
});
|
||||
|
||||
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 debug:
|
||||
res = response.new(500, error);
|
||||
else
|
||||
res = response.new(500);
|
||||
router->plug("/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"});
|
||||
}
|
||||
});
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
s, err = server.new(callback, "127.0.0.1", 8080);
|
||||
if s == none: error(err);
|
||||
s->loop(); # This doesn't return
|
||||
s->close();
|
||||
error(serve("127.0.0.1", 8080, router));
|
||||
@@ -0,0 +1,10 @@
|
||||
|
||||
fun join(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);
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
path = import("path.noja");
|
||||
Request = import("request.noja").Request;
|
||||
respond = import("response.noja").new;
|
||||
Router = {table: Map, plug: Callable, solve: Callable};
|
||||
|
||||
fun loadFile(path: String) {
|
||||
|
||||
stream, error = files.openFile(path, files.READ);
|
||||
if error != none:
|
||||
return none, error;
|
||||
|
||||
text = "";
|
||||
size = 512;
|
||||
do {
|
||||
size = 2 * size;
|
||||
data = buffer.new(size);
|
||||
|
||||
num_bytes, error = files.read(stream, data);
|
||||
if error != none:
|
||||
return none, error;
|
||||
|
||||
text = string.cat(text, buffer.toString(data));
|
||||
|
||||
} while num_bytes == size;
|
||||
|
||||
#files.close(stream);
|
||||
return text;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
fun new() {
|
||||
return {
|
||||
table: {},
|
||||
|
||||
fun plug(router: Router, name: String, methods: Map)
|
||||
router.table[name] = methods;
|
||||
|
||||
|
||||
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 solve(router: Router, req: Request) {
|
||||
|
||||
endpoint = req.url.path;
|
||||
method_table = router.table[endpoint];
|
||||
if method_table == none:
|
||||
return respond(404);
|
||||
|
||||
method_item = method_table[req.method];
|
||||
if method_item == none: {
|
||||
allowed = router->getRouteAllowedMethods(endpoint);
|
||||
if req.method == "OPTIONS":
|
||||
status = 204;
|
||||
else
|
||||
status = 405;
|
||||
return respond(status, none, {"Allow": join(allowed, ", ")});
|
||||
}
|
||||
|
||||
if isCallable(method_item):
|
||||
res = method_item(req);
|
||||
else {
|
||||
cwd = getCurrentWorkingDirectory();
|
||||
file = path.join(cwd, method_item);
|
||||
data, error = loadFile(file);
|
||||
if error == none:
|
||||
res = respond(200, data);
|
||||
else
|
||||
res = respond(500, error);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return {new: new, Router: Router};
|
||||
@@ -1,80 +1,57 @@
|
||||
request = import("request.noja");
|
||||
response = import("response.noja");
|
||||
respond = response.new;
|
||||
Router = import("router.noja").Router;
|
||||
|
||||
Server = {
|
||||
fd: int,
|
||||
callback: Callable,
|
||||
loop : Callable,
|
||||
close: Callable
|
||||
};
|
||||
|
||||
fun handleClient(fd: int, server: Server) {
|
||||
fun handleClient(fd: int, router: Router) {
|
||||
|
||||
req, error, error_is_internal = request.fromSocket(fd);
|
||||
if error != none: {
|
||||
if error_is_internal: {
|
||||
if debug:
|
||||
res = response.new(500, error);
|
||||
else
|
||||
res = response.new(500);
|
||||
} else
|
||||
res = response.new(400, error);
|
||||
if error_is_internal:
|
||||
res = respond(500, error);
|
||||
else
|
||||
res = respond(400, error);
|
||||
} else {
|
||||
if req.version.major != 1:
|
||||
res = response.new(505, "HTTP version not supported");
|
||||
res = respond(505, "HTTP version not supported");
|
||||
else
|
||||
res = server.callback(req);
|
||||
res = router->solve(req);
|
||||
}
|
||||
return response.toSocket(fd, res);
|
||||
}
|
||||
|
||||
fun fallback_callback(request: Request)
|
||||
return response.new(500, "No callback provided");
|
||||
|
||||
return {
|
||||
fun serve(addr: ?String, port: int = 8080, router: Router, backlog=32) {
|
||||
|
||||
Server: Server,
|
||||
server_fd, error = net.socket(net.AF_INET, net.SOCK_STREAM, 0, true);
|
||||
if error != none:
|
||||
return none, error;
|
||||
|
||||
fun new(callback: Callable = fallback_callback,
|
||||
addr : String = "127.0.0.1",
|
||||
port : int = 8080,
|
||||
backlog : int = 32) {
|
||||
|
||||
fd, err = net.socket(net.AF_INET, net.SOCK_STREAM, 0, true);
|
||||
if err != none:
|
||||
return none, err;
|
||||
|
||||
if (err = net.bind(fd, net.AF_INET, port, addr)) != none: {
|
||||
net.close(fd);
|
||||
return none, err;
|
||||
error = net.bind(server_fd, net.AF_INET, port, addr);
|
||||
if error != none: {
|
||||
net.close(server_fd);
|
||||
return none, error;
|
||||
}
|
||||
|
||||
if (err = net.listen(fd, backlog)) != none: {
|
||||
net.close(fd);
|
||||
return none, err;
|
||||
error = net.listen(server_fd, backlog);
|
||||
if error != none: {
|
||||
net.close(server_fd);
|
||||
return none, error;
|
||||
}
|
||||
|
||||
return {
|
||||
fd: fd,
|
||||
callback: callback,
|
||||
while true: {
|
||||
|
||||
fun loop(server: Server) {
|
||||
|
||||
while true: {
|
||||
fd, addr_or_err, port = net.accept(server.fd);
|
||||
if fd == none:
|
||||
error = addr_or_err;
|
||||
else {
|
||||
error = handleClient(fd, server);
|
||||
net.close(fd);
|
||||
}
|
||||
if error != none:
|
||||
print(error);
|
||||
}
|
||||
new_fd, addr_or_err, port = net.accept(server_fd);
|
||||
if new_fd == none:
|
||||
error = addr_or_err;
|
||||
else {
|
||||
error = handleClient(new_fd, router);
|
||||
net.close(new_fd);
|
||||
}
|
||||
if error != none:
|
||||
print(error);
|
||||
}
|
||||
|
||||
fun close(server: Server)
|
||||
net.close(server.fd);
|
||||
};
|
||||
net.close(server_fd);
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user