diff --git a/examples/http_server/main.noja b/examples/http_server/main.noja index 450c77f..0a49f90 100755 --- a/examples/http_server/main.noja +++ b/examples/http_server/main.noja @@ -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) { - - 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) { +router->plug("/", {GET: redirect("/login")}); - username, _, error = getUsername(req); - if error != none: - return respond(400, error); - if username != none: - return respond(400, "You're already logged in"); +router->plug("/home", { - 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': { + fun GET(req: Request) { - GET: 'pages/signup.html', + 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); + } +}); - fun POST(req: Request) { +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"); + 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); + fun areValid(params) { + expect = {user: String, pass: String}; + return istypeof(expect, params) + and count(params.user) > 0 + and count(params.pass) > 0; } - }, - '/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"); + params = urlencoded.parse(req.body); + if not areValid(params): + return respond(400, "Invalid parameters"); + username = params.user; + password = params.pass; - session_table[session_id] = none; + 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 - return respond(303, "Goodbye!", {"Location": "/login"}); + # 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); + } +}); + +router->plug("/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); } -}; +}); -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("/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 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) { - - endpoint = req.url.path; - method_table = route_table[endpoint]; - if method_table == none: - return response.new(404); - - 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, ", ")}); - } - - 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); - } - - 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)); \ No newline at end of file diff --git a/examples/http_server/path.noja b/examples/http_server/path.noja new file mode 100644 index 0000000..163f336 --- /dev/null +++ b/examples/http_server/path.noja @@ -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); +} \ No newline at end of file diff --git a/examples/http_server/router.noja b/examples/http_server/router.noja new file mode 100644 index 0000000..f441760 --- /dev/null +++ b/examples/http_server/router.noja @@ -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}; \ No newline at end of file diff --git a/examples/http_server/server.noja b/examples/http_server/server.noja index 9969c06..8c8100b 100644 --- a/examples/http_server/server.noja +++ b/examples/http_server/server.noja @@ -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 { - - Server: Server, + fun serve(addr: ?String, port: int = 8080, router: Router, backlog=32) { - fun new(callback: Callable = fallback_callback, - addr : String = "127.0.0.1", - port : int = 8080, - backlog : int = 32) { + server_fd, error = net.socket(net.AF_INET, net.SOCK_STREAM, 0, true); + if error != none: + return none, error; - 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); } }; \ No newline at end of file