From edab5e3235d71b4d2aca56468cde86139c45d483 Mon Sep 17 00:00:00 2001 From: Leonard Schuetz Date: Mon, 20 Mar 2017 08:32:15 +0100 Subject: [PATCH 1/8] Added Function#run and Function#run_with_context --- src/charly/interpreter/internals/functions.cr | 22 +++++++++++++++++++ src/std/primitives/function.ch | 22 +++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/src/charly/interpreter/internals/functions.cr b/src/charly/interpreter/internals/functions.cr index ccf28fc5..6351fb69 100644 --- a/src/charly/interpreter/internals/functions.cr +++ b/src/charly/interpreter/internals/functions.cr @@ -15,6 +15,28 @@ module Charly::Internals return function end + charly_api "function_run", TFunc, TArray do |function, arguments| + return visitor.run_function_call( + function, + arguments.value, + nil, + scope, + context, + call.location_start + ) + end + + charly_api "function_run_with_context", TFunc, BaseType, TArray do |function, ctx, arguments| + return visitor.run_function_call( + function, + arguments.value, + ctx, + scope, + context, + call.location_start + ) + end + charly_api "is_internal", BaseType do |function| return TBoolean.new function.is_a?(TInternalFunc) end diff --git a/src/std/primitives/function.ch b/src/std/primitives/function.ch index 548e1866..8c8f1e0a 100644 --- a/src/std/primitives/function.ch +++ b/src/std/primitives/function.ch @@ -1,5 +1,7 @@ const is_internal = __internal__method("is_internal") const function_bind = __internal__method("function_bind") +const function_run = __internal__method("function_run") +const function_run_with_context = __internal__method("function_run_with_context") export = primitive class Function { @@ -15,4 +17,24 @@ export = primitive class Function { const bound_arguments = arguments.range(1, arguments.length()) return function_bind(self, context, bound_arguments) } + + /* + * Runs the function in the standard context with the arguments inside + * *arguments* + **/ + func run(arguments) { + if typeof arguments ! "Array" { + throw Exception("Expected argument to be an array") + } + + return function_run(self, arguments) + } + + func run_with_context(context, arguments) { + if typeof arguments ! "Array" { + throw Exception("Expected argument to be an array") + } + + return function_run_with_context(self, context, arguments) + } } From 615848bfadcb1a47f87a8568f00591cd00fc0df7 Mon Sep 17 00:00:00 2001 From: Leonard Schuetz Date: Mon, 20 Mar 2017 15:27:03 +0100 Subject: [PATCH 2/8] Fixed invalid scope in Function#run and Function#run_with_context --- src/charly/interpreter/internals/functions.cr | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/charly/interpreter/internals/functions.cr b/src/charly/interpreter/internals/functions.cr index 6351fb69..7973136f 100644 --- a/src/charly/interpreter/internals/functions.cr +++ b/src/charly/interpreter/internals/functions.cr @@ -20,7 +20,7 @@ module Charly::Internals function, arguments.value, nil, - scope, + function.parent_scope, context, call.location_start ) @@ -31,7 +31,7 @@ module Charly::Internals function, arguments.value, ctx, - scope, + function.parent_scope, context, call.location_start ) From e5848a5131218bd527bc0749734b5fdee2045635 Mon Sep 17 00:00:00 2001 From: Leonard Schuetz Date: Mon, 20 Mar 2017 23:24:56 +0100 Subject: [PATCH 3/8] Added basic working example of net module --- examples/webserver.ch | 19 +++ src/charly/interpreter/internals/net.cr | 150 ++++++++++++++++++++++++ src/charly/interpreter/require.cr | 1 + src/std/modules/net.ch | 1 + src/std/modules/net/http.ch | 14 +++ src/std/modules/net/request.ch | 5 + src/std/modules/net/response.ch | 5 + src/std/modules/net/server.ch | 42 +++++++ 8 files changed, 237 insertions(+) create mode 100644 examples/webserver.ch create mode 100644 src/charly/interpreter/internals/net.cr create mode 100644 src/std/modules/net.ch create mode 100644 src/std/modules/net/http.ch create mode 100644 src/std/modules/net/request.ch create mode 100644 src/std/modules/net/response.ch create mode 100644 src/std/modules/net/server.ch diff --git a/examples/webserver.ch b/examples/webserver.ch new file mode 100644 index 00000000..222adee7 --- /dev/null +++ b/examples/webserver.ch @@ -0,0 +1,19 @@ +const http = require("net") +const server = http.create_server("localhost", 8080) + +let count = 0 + +server.on("request", ->(req, res) { + res.body = "" + count + count += 1 +}) + +server.on("listen", ->{ + print("listening on localhost:8080") +}) + +server.on("close", ->{ + print("closing server") +}) + +server.listen() diff --git a/src/charly/interpreter/internals/net.cr b/src/charly/interpreter/internals/net.cr new file mode 100644 index 00000000..3cb15102 --- /dev/null +++ b/src/charly/interpreter/internals/net.cr @@ -0,0 +1,150 @@ +require "../**" +require "colorize" +require "http" + +module Charly::Internals + + # Pool of all current open servers + HTTP_SERVERS = {} of UInt64 => Server + + class Server + @@next_server_id = 1_u64 # TODO: Put this at a more suitable place + + def self.next_server_id + @@next_server_id + end + + def self.next_server_id=(value) + @@next_server_id = value + end + + property server : HTTP::Server + property handler : TObject + property on_request : Proc(TObject, TObject, Void)? + property on_listen : Proc(Void)? + property on_close : Proc(Void)? + + def initialize(@handler, address, port) + @server = HTTP::Server.new(address, port) do |context| + wrapped_request = wrap_request context.request + wrapped_response = wrap_response context.response + @on_request.try &.call wrapped_request, wrapped_response + copy_to_response wrapped_response, context.response + end + end + + def on_request(&block : Proc(TObject, TObject, Void)) + @on_request = block + end + + def on_listen(&block) + @on_listen = block + end + + def on_close(&block) + @on_close = block + end + + def listen + @server.listen + end + + private def wrap_request(req : HTTP::Request) + TObject.new do |data| + data.init "body", TString.new req.body.to_s + data.init "content_length", TNumeric.new req.content_length || 0 + data.init "host", TString.new req.host || "" + data.init "ignore_body", TBoolean.new req.ignore_body? + data.init "keep_alive", TBoolean.new req.keep_alive? + data.init "method", TString.new req.method + data.init "path", TString.new req.path + data.init "query", TString.new req.query || "" + data.init "resource", TString.new req.resource + data.init "version", TString.new req.version + end + end + + private def wrap_response(res : HTTP::Server::Response) + TObject.new do |data| + data.init "body", TString.new "" + data.init "status_code", TNumeric.new 200 + end + end + + private def copy_to_response(source : TObject, res : HTTP::Server::Response) + res.output.print source.data.get("body").as(TString).value rescue "" + res.status_code = source.data.get("status_code").as(TNumeric).value.to_i32 rescue 200 + end + end + + charly_api "net_create", TObject, TString, TNumeric do |handler, address, port| + address, port = address.value, port.value.to_i32 + + server = Server.new handler, address, port + event_handler = handler.data.get "invoke" + + server.on_request do |request, response| + invoke = event_handler.as(TFunc) + visitor.run_function_call( + invoke, + [ + TString.new("request"), + TArray.new([request, response] of BaseType) + ] of BaseType, + server.handler, + invoke.parent_scope, + context, + call.location_start + ) + end + + server.on_listen do + invoke = event_handler.as(TFunc) + visitor.run_function_call( + invoke, + [ + TString.new("listen"), + TArray.new + ] of BaseType, + server.handler, + invoke.parent_scope, + context, + call.location_start + ) + end + + server.on_close do + invoke = event_handler.as(TFunc) + visitor.run_function_call( + invoke, + [ + TString.new("close"), + TArray.new + ] of BaseType, + server.handler, + invoke.parent_scope, + context, + call.location_start + ) + end + + HTTP_SERVERS[Server.next_server_id] = server + return TNumeric.new(Server.next_server_id).tap do + Server.next_server_id += 1 + end + end + + charly_api "net_listen", TNumeric do |id| + id = id.value.to_u64 + + server = HTTP_SERVERS[id]? + + if server + server.on_listen.try &.call + server.listen + end + + TNull.new + end + +end diff --git a/src/charly/interpreter/require.cr b/src/charly/interpreter/require.cr index 013b4826..4356e124 100644 --- a/src/charly/interpreter/require.cr +++ b/src/charly/interpreter/require.cr @@ -18,6 +18,7 @@ module Charly::Require "charly", "fs", "repl", + "net", ] of String # Loads *filename* and returns the value of the export variable diff --git a/src/std/modules/net.ch b/src/std/modules/net.ch new file mode 100644 index 00000000..42829244 --- /dev/null +++ b/src/std/modules/net.ch @@ -0,0 +1 @@ +export = require("net/http.ch") diff --git a/src/std/modules/net/http.ch b/src/std/modules/net/http.ch new file mode 100644 index 00000000..d8951d75 --- /dev/null +++ b/src/std/modules/net/http.ch @@ -0,0 +1,14 @@ +const Server = require("./server.ch") +const Request = Server.Request +const Response = Server.Response + +class HTTP { + static func create_server(address, port) { + Server(address, port) + } +} + +export = HTTP +export.Server = Server +export.Request = Request +export.Response = Response diff --git a/src/std/modules/net/request.ch b/src/std/modules/net/request.ch new file mode 100644 index 00000000..0142f5da --- /dev/null +++ b/src/std/modules/net/request.ch @@ -0,0 +1,5 @@ +class Request { + +} + +export = Request diff --git a/src/std/modules/net/response.ch b/src/std/modules/net/response.ch new file mode 100644 index 00000000..09ad85cf --- /dev/null +++ b/src/std/modules/net/response.ch @@ -0,0 +1,5 @@ +class Response { + +} + +export = Response diff --git a/src/std/modules/net/server.ch b/src/std/modules/net/server.ch new file mode 100644 index 00000000..f3928c69 --- /dev/null +++ b/src/std/modules/net/server.ch @@ -0,0 +1,42 @@ +const net_create = __internal__method("net_create") +const net_listen = __internal__method("net_listen") + +const Request = require("./request.ch") +const Response = require("./response.ch") + +class Server { + property id + property address + property port + property events + + func constructor(address, port) { + @id = net_create(self, address, port) + @address = address + @port = port + @events = {} + } + + func listen() { + net_listen(@id) + } + + func on(name, callback) { + @events[name] = callback + } + + /* + * This is the method net_listen will delegate any events to + **/ + func invoke(name, arguments) { + const handler = @events[name] + + if typeof handler == "Function" { + return handler.run(arguments) + } + } +} + +export = Server +export.Request = Request +export.Response = Response From eea59087055a35786dff1c8bf0b861d1cbfc271a Mon Sep 17 00:00:00 2001 From: Leonard Schuetz Date: Mon, 20 Mar 2017 23:30:01 +0100 Subject: [PATCH 4/8] Added Server#close --- examples/webserver.ch | 4 ++++ src/charly/interpreter/internals/net.cr | 17 +++++++++++++++++ src/std/modules/net/server.ch | 5 +++++ 3 files changed, 26 insertions(+) diff --git a/examples/webserver.ch b/examples/webserver.ch index 222adee7..892d8e90 100644 --- a/examples/webserver.ch +++ b/examples/webserver.ch @@ -6,6 +6,10 @@ let count = 0 server.on("request", ->(req, res) { res.body = "" + count count += 1 + + if count > 10 { + server.close() + } }) server.on("listen", ->{ diff --git a/src/charly/interpreter/internals/net.cr b/src/charly/interpreter/internals/net.cr index 3cb15102..1c41960c 100644 --- a/src/charly/interpreter/internals/net.cr +++ b/src/charly/interpreter/internals/net.cr @@ -49,6 +49,10 @@ module Charly::Internals @server.listen end + def close + @server.close + end + private def wrap_request(req : HTTP::Request) TObject.new do |data| data.init "body", TString.new req.body.to_s @@ -147,4 +151,17 @@ module Charly::Internals TNull.new end + charly_api "net_close", TNumeric do |id| + id = id.value.to_u64 + + server = HTTP_SERVERS[id]? + + if server + server.close + server.on_close.try &.call + end + + TNull.new + end + end diff --git a/src/std/modules/net/server.ch b/src/std/modules/net/server.ch index f3928c69..db892523 100644 --- a/src/std/modules/net/server.ch +++ b/src/std/modules/net/server.ch @@ -1,5 +1,6 @@ const net_create = __internal__method("net_create") const net_listen = __internal__method("net_listen") +const net_close = __internal__method("net_close") const Request = require("./request.ch") const Response = require("./response.ch") @@ -21,6 +22,10 @@ class Server { net_listen(@id) } + func close() { + net_close(@id) + } + func on(name, callback) { @events[name] = callback } From bdb51626410bc3102abfba7ad3a3619b334e1922 Mon Sep 17 00:00:00 2001 From: Leonard Schuetz Date: Tue, 21 Mar 2017 21:48:58 +0100 Subject: [PATCH 5/8] Add cookies, query_params and headers --- examples/webserver.ch | 9 +------ src/charly/interpreter/internals/net.cr | 33 +++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/examples/webserver.ch b/examples/webserver.ch index 892d8e90..907d564f 100644 --- a/examples/webserver.ch +++ b/examples/webserver.ch @@ -1,15 +1,8 @@ const http = require("net") const server = http.create_server("localhost", 8080) -let count = 0 - server.on("request", ->(req, res) { - res.body = "" + count - count += 1 - - if count > 10 { - server.close() - } + res.body = req.to_s() }) server.on("listen", ->{ diff --git a/src/charly/interpreter/internals/net.cr b/src/charly/interpreter/internals/net.cr index 1c41960c..d9ad9861 100644 --- a/src/charly/interpreter/internals/net.cr +++ b/src/charly/interpreter/internals/net.cr @@ -53,6 +53,7 @@ module Charly::Internals @server.close end + # Creates a TObject from a HTTP::Request private def wrap_request(req : HTTP::Request) TObject.new do |data| data.init "body", TString.new req.body.to_s @@ -65,9 +66,40 @@ module Charly::Internals data.init "query", TString.new req.query || "" data.init "resource", TString.new req.resource data.init "version", TString.new req.version + + data.init "query_params", TObject.new { |data| + req.query_params.each do |(name, value)| + data.init name, TString.new value + end + } + + data.init "headers", TObject.new { |data| + req.headers.each do |(name, value)| + values = TArray.new value.map { |field| TString.new(field).as(BaseType) } + data.init name, values + end + } + + data.init "cookies", TObject.new { |data| + req.cookies.each do |cookie| + data.init cookie.name, TObject.new { |data| + data.init "value", TString.new cookie.value + data.init "path", TString.new cookie.path + data.init "secure", TBoolean.new cookie.secure + data.init "http_only", TBoolean.new cookie.http_only + + expires, domain, extension = cookie.expires, cookie.domain, cookie.extension + + data.init "expires", TNumeric.new expires.epoch if expires + data.init "domain", TString.new domain if domain + data.init "extension", TString.new extension if extension + } + end + } end end + # Creates a TObject from a HTTP::Server::Response private def wrap_response(res : HTTP::Server::Response) TObject.new do |data| data.init "body", TString.new "" @@ -75,6 +107,7 @@ module Charly::Internals end end + # Copies values from a TObject into a HTTP::Server::Response object private def copy_to_response(source : TObject, res : HTTP::Server::Response) res.output.print source.data.get("body").as(TString).value rescue "" res.status_code = source.data.get("status_code").as(TNumeric).value.to_i32 rescue 200 From 66be73ac8b839ac2f8d198b5d28159871f54a291 Mon Sep 17 00:00:00 2001 From: Leonard Schuetz Date: Tue, 21 Mar 2017 22:25:22 +0100 Subject: [PATCH 6/8] Keep track of all responses in a global table --- src/charly/interpreter/internals/net.cr | 25 ++++++++++++++++++++++--- src/std/modules/net/request.ch | 4 ++++ src/std/modules/net/response.ch | 4 ++++ src/std/modules/net/server.ch | 6 ++++++ 4 files changed, 36 insertions(+), 3 deletions(-) diff --git a/src/charly/interpreter/internals/net.cr b/src/charly/interpreter/internals/net.cr index d9ad9861..601a8550 100644 --- a/src/charly/interpreter/internals/net.cr +++ b/src/charly/interpreter/internals/net.cr @@ -6,9 +6,12 @@ module Charly::Internals # Pool of all current open servers HTTP_SERVERS = {} of UInt64 => Server + HTTP_RESPONSES = {} of UInt64 => HTTP::Server::Response class Server - @@next_server_id = 1_u64 # TODO: Put this at a more suitable place + # TODO: Put these at a more suitable place + @@next_server_id = 1_u64 + @@next_response_id = 1_u64 def self.next_server_id @@next_server_id @@ -18,6 +21,14 @@ module Charly::Internals @@next_server_id = value end + def self.next_response_id + @@next_response_id + end + + def self.next_response_id=(value) + @@next_response_id = value + end + property server : HTTP::Server property handler : TObject property on_request : Proc(TObject, TObject, Void)? @@ -26,8 +37,15 @@ module Charly::Internals def initialize(@handler, address, port) @server = HTTP::Server.new(address, port) do |context| + + # Registers this response in the global response table + response_id = Server.next_response_id + HTTP_RESPONSES[Server.next_response_id] = context.response + Server.next_response_id += 1 + + # Wraps request and response objects to be passed to charly space wrapped_request = wrap_request context.request - wrapped_response = wrap_response context.response + wrapped_response = wrap_response context.response, response_id @on_request.try &.call wrapped_request, wrapped_response copy_to_response wrapped_response, context.response end @@ -100,8 +118,9 @@ module Charly::Internals end # Creates a TObject from a HTTP::Server::Response - private def wrap_response(res : HTTP::Server::Response) + private def wrap_response(res : HTTP::Server::Response, response_id : UInt64) TObject.new do |data| + data.init "__response_id", TNumeric.new response_id data.init "body", TString.new "" data.init "status_code", TNumeric.new 200 end diff --git a/src/std/modules/net/request.ch b/src/std/modules/net/request.ch index 0142f5da..bcdc30c6 100644 --- a/src/std/modules/net/request.ch +++ b/src/std/modules/net/request.ch @@ -1,5 +1,9 @@ class Request { + property data + func constructor(data) { + @data = data + } } export = Request diff --git a/src/std/modules/net/response.ch b/src/std/modules/net/response.ch index 09ad85cf..6ac80a3c 100644 --- a/src/std/modules/net/response.ch +++ b/src/std/modules/net/response.ch @@ -1,5 +1,9 @@ class Response { + property data + func constructor(data) { + @data = data + } } export = Response diff --git a/src/std/modules/net/server.ch b/src/std/modules/net/server.ch index db892523..9ddac4c9 100644 --- a/src/std/modules/net/server.ch +++ b/src/std/modules/net/server.ch @@ -37,6 +37,12 @@ class Server { const handler = @events[name] if typeof handler == "Function" { + + if name == "request" { + arguments[0] = Request(arguments[0]) + arguments[1] = Response(arguments[1]) + } + return handler.run(arguments) } } From eddb00788ee43849289525087c3b1db326ae0c90 Mon Sep 17 00:00:00 2001 From: Leonard Schuetz Date: Wed, 22 Mar 2017 21:22:45 +0100 Subject: [PATCH 7/8] Mapped headers and output in response --- src/charly/interpreter/internals/net.cr | 97 ++++++++++++++++++++++++- src/std/modules/net/request.ch | 28 ++++++- src/std/modules/net/response.ch | 56 +++++++++++++- 3 files changed, 173 insertions(+), 8 deletions(-) diff --git a/src/charly/interpreter/internals/net.cr b/src/charly/interpreter/internals/net.cr index 601a8550..df92c3f6 100644 --- a/src/charly/interpreter/internals/net.cr +++ b/src/charly/interpreter/internals/net.cr @@ -40,7 +40,7 @@ module Charly::Internals # Registers this response in the global response table response_id = Server.next_response_id - HTTP_RESPONSES[Server.next_response_id] = context.response + HTTP_RESPONSES[response_id] = context.response Server.next_response_id += 1 # Wraps request and response objects to be passed to charly space @@ -48,6 +48,8 @@ module Charly::Internals wrapped_response = wrap_response context.response, response_id @on_request.try &.call wrapped_request, wrapped_response copy_to_response wrapped_response, context.response + + HTTP_RESPONSES.delete response_id end end @@ -121,15 +123,46 @@ module Charly::Internals private def wrap_response(res : HTTP::Server::Response, response_id : UInt64) TObject.new do |data| data.init "__response_id", TNumeric.new response_id - data.init "body", TString.new "" data.init "status_code", TNumeric.new 200 + + data.init "headers", TObject.new { |data| + res.headers.each do |(name, value)| + values = TArray.new value.map { |field| TString.new(field).as(BaseType) } + data.init name, values + end + } + + res.headers.clear end end # Copies values from a TObject into a HTTP::Server::Response object private def copy_to_response(source : TObject, res : HTTP::Server::Response) - res.output.print source.data.get("body").as(TString).value rescue "" - res.status_code = source.data.get("status_code").as(TNumeric).value.to_i32 rescue 200 + if source.data.contains "status_code" + status_code = source.data["status_code"] + + if status_code.is_a? TNumeric + res.status_code = status_code.value.to_i32 + end + end + + if source.data.contains "headers" + headers = source.data["headers"] + + if headers.is_a? TObject + headers.data.dump_values(false).each do |(_, key, value, _)| + + if value.is_a? TArray + value.value.each do |field| + + if field.is_a? TString + res.headers.add key, field.value + end + end + end + end + end + end end end @@ -216,4 +249,60 @@ module Charly::Internals TNull.new end + charly_api "net_response_flush", TNumeric do |rid| + response = HTTP_RESPONSES[rid.value.to_i32]? + + unless response + raise RunTimeError.new(call, context, "No response with id #{rid}") + end + + response.flush + + TNull.new + end + + charly_api "net_response_close", TNumeric do |rid| + response = HTTP_RESPONSES[rid.value.to_i32]? + + unless response + raise RunTimeError.new(call, context, "No response with id #{rid}") + end + + response.close + + TNull.new + end + + charly_api "net_response_write", TNumeric, TString do |rid, data| + response = HTTP_RESPONSES[rid.value.to_i32]? + + unless response + raise RunTimeError.new(call, context, "No response with id #{rid}") + end + + data = data.value + response.output.print data + + TNull.new + end + + charly_api "net_response_write_file", TNumeric, TString do |rid, path| + response = HTTP_RESPONSES[rid.value.to_i32]? + + unless response + raise RunTimeError.new(call, context, "No response with id #{rid}") + end + + path = path.value + path = File.expand_path path + + unless File.readable? path + raise RunTimeError.new(call, context, "Can't open file at #{path}") + end + + response.output.print File.read path + + TNull.new + end + end diff --git a/src/std/modules/net/request.ch b/src/std/modules/net/request.ch index bcdc30c6..097d0836 100644 --- a/src/std/modules/net/request.ch +++ b/src/std/modules/net/request.ch @@ -1,8 +1,32 @@ class Request { - property data + property body + property content_length + property host + property ignore_body + property keep_alive + property method + property path + property query + property resource + property version + property query_params + property headers + property cookies func constructor(data) { - @data = data + @body = data.body + @content_length = data.content_length + @host = data.host + @ignore_body = data.ignore_body + @keep_alive = data.keep_alive + @method = data.method + @path = data.path + @query = data.query + @resource = data.resource + @version = data.version + @query_params = data.query_params + @headers = data.headers + @cookies = data.cookies } } diff --git a/src/std/modules/net/response.ch b/src/std/modules/net/response.ch index 6ac80a3c..ed72ab51 100644 --- a/src/std/modules/net/response.ch +++ b/src/std/modules/net/response.ch @@ -1,8 +1,60 @@ +const net_response_flush = __internal__method("net_response_flush") +const net_response_close = __internal__method("net_response_close") +const net_response_write = __internal__method("net_response_write") +const net_response_write_file = __internal__method("net_response_write_file") + class Response { - property data + property response_id + property status_code + property headers func constructor(data) { - @data = data + @response_id = data.__response_id + @status_code = data.status_code + @headers = data.headers + } + + /** + * Flushes the output + **/ + func flush() { + net_response_flush(@response_id) + self + } + + /** + * Closes the response, writing headers and body if not done yet + **/ + func close() { + net_response_close(@response_id) + self + } + + /** + * Writes the string representation of data to the response + **/ + func write(data) { + net_response_write(@response_id, data.to_s()) + self + } + + /** + * Writes a given File object to the response + **/ + func write_file(path) { + net_response_write_file(@response_id, path) + } + + func set_header(name, value) { + const values = @headers[name] + + if typeof values == "Array" { + values << value.to_s() + } else { + @headers[name] = [value.to_s()] + } + + self } } From 154d3ca130898c97f22ba10103d43459bd8d0fe1 Mon Sep 17 00:00:00 2001 From: Leonard Schuetz Date: Thu, 23 Mar 2017 22:18:55 +0100 Subject: [PATCH 8/8] More progress --- examples/data/index.html | 19 +++++++++ examples/webserver.ch | 6 ++- src/charly/interpreter/internals/net.cr | 57 +++++-------------------- src/std/modules/net/response.ch | 28 +----------- src/std/modules/net/server.ch | 24 +++++++++-- 5 files changed, 57 insertions(+), 77 deletions(-) create mode 100644 examples/data/index.html diff --git a/examples/data/index.html b/examples/data/index.html new file mode 100644 index 00000000..40bc1c56 --- /dev/null +++ b/examples/data/index.html @@ -0,0 +1,19 @@ + + + + + hello world + + + +

Hello World

+

+ Welcome to a webserver written in Charly + The current time in milliseconds is {{time}} +

+ + diff --git a/examples/webserver.ch b/examples/webserver.ch index 907d564f..c733235a 100644 --- a/examples/webserver.ch +++ b/examples/webserver.ch @@ -1,8 +1,12 @@ const http = require("net") +const fs = require("fs") const server = http.create_server("localhost", 8080) server.on("request", ->(req, res) { - res.body = req.to_s() + res.set_header("Content-type", "text/html") + res.body = fs.read("examples/data/index.html", "utf8") + + res.body = res.body.split("{{time}}").join(io.time_ms()) }) server.on("listen", ->{ diff --git a/src/charly/interpreter/internals/net.cr b/src/charly/interpreter/internals/net.cr index df92c3f6..424965a7 100644 --- a/src/charly/interpreter/internals/net.cr +++ b/src/charly/interpreter/internals/net.cr @@ -47,7 +47,10 @@ module Charly::Internals wrapped_request = wrap_request context.request wrapped_response = wrap_response context.response, response_id @on_request.try &.call wrapped_request, wrapped_response - copy_to_response wrapped_response, context.response + + unless context.response.output.closed? + copy_to_response wrapped_response, context.response + end HTTP_RESPONSES.delete response_id end @@ -124,6 +127,7 @@ module Charly::Internals TObject.new do |data| data.init "__response_id", TNumeric.new response_id data.init "status_code", TNumeric.new 200 + data.init "body", TString.new "" data.init "headers", TObject.new { |data| res.headers.each do |(name, value)| @@ -146,6 +150,12 @@ module Charly::Internals end end + if source.data.contains "body" + body = source.data["body"] + body = "#{body}" + res.output.print body + end + if source.data.contains "headers" headers = source.data["headers"] @@ -249,18 +259,6 @@ module Charly::Internals TNull.new end - charly_api "net_response_flush", TNumeric do |rid| - response = HTTP_RESPONSES[rid.value.to_i32]? - - unless response - raise RunTimeError.new(call, context, "No response with id #{rid}") - end - - response.flush - - TNull.new - end - charly_api "net_response_close", TNumeric do |rid| response = HTTP_RESPONSES[rid.value.to_i32]? @@ -272,37 +270,4 @@ module Charly::Internals TNull.new end - - charly_api "net_response_write", TNumeric, TString do |rid, data| - response = HTTP_RESPONSES[rid.value.to_i32]? - - unless response - raise RunTimeError.new(call, context, "No response with id #{rid}") - end - - data = data.value - response.output.print data - - TNull.new - end - - charly_api "net_response_write_file", TNumeric, TString do |rid, path| - response = HTTP_RESPONSES[rid.value.to_i32]? - - unless response - raise RunTimeError.new(call, context, "No response with id #{rid}") - end - - path = path.value - path = File.expand_path path - - unless File.readable? path - raise RunTimeError.new(call, context, "Can't open file at #{path}") - end - - response.output.print File.read path - - TNull.new - end - end diff --git a/src/std/modules/net/response.ch b/src/std/modules/net/response.ch index ed72ab51..5b23edf4 100644 --- a/src/std/modules/net/response.ch +++ b/src/std/modules/net/response.ch @@ -1,27 +1,18 @@ -const net_response_flush = __internal__method("net_response_flush") const net_response_close = __internal__method("net_response_close") -const net_response_write = __internal__method("net_response_write") -const net_response_write_file = __internal__method("net_response_write_file") class Response { property response_id property status_code + property body property headers func constructor(data) { @response_id = data.__response_id @status_code = data.status_code + @body = data.body @headers = data.headers } - /** - * Flushes the output - **/ - func flush() { - net_response_flush(@response_id) - self - } - /** * Closes the response, writing headers and body if not done yet **/ @@ -30,21 +21,6 @@ class Response { self } - /** - * Writes the string representation of data to the response - **/ - func write(data) { - net_response_write(@response_id, data.to_s()) - self - } - - /** - * Writes a given File object to the response - **/ - func write_file(path) { - net_response_write_file(@response_id, path) - } - func set_header(name, value) { const values = @headers[name] diff --git a/src/std/modules/net/server.ch b/src/std/modules/net/server.ch index 9ddac4c9..a88d795f 100644 --- a/src/std/modules/net/server.ch +++ b/src/std/modules/net/server.ch @@ -39,11 +39,27 @@ class Server { if typeof handler == "Function" { if name == "request" { - arguments[0] = Request(arguments[0]) - arguments[1] = Response(arguments[1]) - } + const request = arguments[0] + const response = arguments[1] + + // Both request and response are wrapped in their own + // objects so we can have methods on them + const wrapped_request = arguments[0] = Request(arguments[0]) + const wrapped_response = arguments[1] = Response(arguments[1]) + + const result = handler.run(arguments) - return handler.run(arguments) + // Copies over all keys from the wrapped response + // to the original response object provided by + // the native functions + Object.keys(wrapped_response).each(->(key) { + response[key] = wrapped_response[key] + }) + + return result + } else { + return handler.run(arguments) + } } } }