diff --git a/spec/context_spec.cr b/spec/context_spec.cr index 0148d43..ea1dbb6 100644 --- a/spec/context_spec.cr +++ b/spec/context_spec.cr @@ -1,102 +1,72 @@ require "./spec_helper" describe "Context" do - context "headers" do - it "sets content type" do - get "/" do |env| - env.response.content_type = "application/json" - "Hello" - end - request = HTTP::Request.new("GET", "/") - client_response = call_request_on_app(request) - client_response.headers["Content-Type"].should eq("application/json") - end - - it "parses headers" do - get "/" do |env| - name = env.request.headers["name"] - "Hello #{name}" - end - headers = HTTP::Headers.new - headers["name"] = "kemal" - request = HTTP::Request.new("GET", "/", headers) - client_response = call_request_on_app(request) - client_response.body.should eq "Hello kemal" - end - - it "sets response headers" do - get "/" do |env| - env.response.headers.add "Accept-Language", "tr" - end - request = HTTP::Request.new("GET", "/") - client_response = call_request_on_app(request) - client_response.headers["Accept-Language"].should eq "tr" + it "sets content type" do + app = Kemal::Base.new + app.get "/" do |env| + env.response.content_type = "application/json" + "Hello" end + request = HTTP::Request.new("GET", "/") + client_response = call_request_on_app(app, request) + client_response.headers["Content-Type"].should eq("application/json") end - context "storage" do - it "can store primitive types" do - before_get "/" do |env| - env.set "before_get", "Kemal" - env.set "before_get_int", 123 - env.set "before_get_float", 3.5 - end + it "parses headers" do + app = Kemal::Base.new + app.get "/" do |env| + name = env.request.headers["name"] + "Hello #{name}" + end + headers = HTTP::Headers.new + headers["name"] = "kemal" + request = HTTP::Request.new("GET", "/", headers) + client_response = call_request_on_app(app, request) + client_response.body.should eq "Hello kemal" + end - get "/" do |env| - { - before_get: env.get("before_get"), - before_get_int: env.get("before_get_int"), - before_get_float: env.get("before_get_float"), - } - end + it "sets response headers" do + app = Kemal::Base.new + app.get "/" do |env| + env.response.headers.add "Accept-Language", "tr" + end + request = HTTP::Request.new("GET", "/") + client_response = call_request_on_app(app, request) + client_response.headers["Accept-Language"].should eq "tr" + end - request = HTTP::Request.new("GET", "/") - io = IO::Memory.new - response = HTTP::Server::Response.new(io) - context = HTTP::Server::Context.new(request, response) - Kemal::FilterHandler::INSTANCE.call(context) - Kemal::RouteHandler::INSTANCE.call(context) - - context.get("before_get").should eq "Kemal" - context.get("before_get_int").should eq 123 - context.get("before_get_float").should eq 3.5 + it "can store variables" do + app = Kemal::Base.new + app.before_get "/" do |env| + t = TestContextStorageType.new + t.id = 32 + a = AnotherContextStorageType.new + env.set "key", "value" + env.set "before_get", "Kemal" + env.set "before_get_int", 123 + env.set "before_get_context_test", t + env.set "another_context_test", a + env.set "before_get_float", 3.5 end - it "can store custom types" do - before_get "/" do |env| - t = TestContextStorageType.new - t.id = 32 - a = AnotherContextStorageType.new - - env.set "before_get_context_test", t - env.set "another_context_test", a - end - - get "/" do |env| - { - before_get_context_test: env.get("before_get_context_test"), - another_context_test: env.get("another_context_test"), - } - end - - request = HTTP::Request.new("GET", "/") - io = IO::Memory.new - response = HTTP::Server::Response.new(io) - context = HTTP::Server::Context.new(request, response) - Kemal::FilterHandler::INSTANCE.call(context) - Kemal::RouteHandler::INSTANCE.call(context) - - context.get("before_get_context_test").as(TestContextStorageType).id.should eq 32 - context.get("another_context_test").as(AnotherContextStorageType).name.should eq "kemal-context" + app.get "/" do |env| + env.set "key", "value" + { + key: env.get("key"), + before_get: env.get("before_get"), + before_get_int: env.get("before_get_int"), + before_get_float: env.get("before_get_float"), + before_get_context_test: env.get("before_get_context_test"), + } end request = HTTP::Request.new("GET", "/") io = IO::Memory.new response = HTTP::Server::Response.new(io) context = HTTP::Server::Context.new(request, response) - context.app = Kemal.application - Kemal.application.filter_handler.call(context) - Kemal.application.route_handler.call(context) + context.app = app + app.filter_handler.call(context) + app.route_handler.call(context) context.store["key"].should eq "value" context.store["before_get"].should eq "Kemal" context.store["before_get_int"].should eq 123 diff --git a/spec/dsl_helper.cr b/spec/dsl_helper.cr new file mode 100644 index 0000000..5aa05ee --- /dev/null +++ b/spec/dsl_helper.cr @@ -0,0 +1,58 @@ +require "./spec_helper" +require "../src/kemal/dsl" + +include Kemal + +class CustomLogHandler < Kemal::BaseLogHandler + def call(env) + call_next env + end + + def write(message) + end +end + +class TestContextStorageType + property id + @id = 1 + + def to_s + @id + end +end + +class AnotherContextStorageType + property name + @name = "kemal-context" +end + +add_context_storage_type(TestContextStorageType) +add_context_storage_type(AnotherContextStorageType) + +def create_request_and_return_io(handler, request) + io = IO::Memory.new + response = HTTP::Server::Response.new(io) + context = HTTP::Server::Context.new(request, response) + context.app = Kemal.application + handler.call(context) + response.close + io.rewind + io +end + +def call_request_on_app(request) + call_request_on_app(Kemal.application, request) +end + +def build_main_handler + build_main_handler(Kemal.application) +end + +Spec.before_each do + config = Kemal.config + config.env = "development" +end + +Spec.after_each do + Kemal.application.clear +end diff --git a/spec/exception_handler_spec.cr b/spec/exception_handler_spec.cr index bfa875d..95df288 100644 --- a/spec/exception_handler_spec.cr +++ b/spec/exception_handler_spec.cr @@ -1,18 +1,13 @@ -require "./spec_helper" - -private INSTANCE = Kemal::ExceptionHandler.new +require "./dsl_helper" describe "Kemal::ExceptionHandler" do it "renders 404 on route not found" do - get "/" do - "Hello" - end - request = HTTP::Request.new("GET", "/asd") io = IO::Memory.new response = HTTP::Server::Response.new(io) context = HTTP::Server::Context.new(request, response) - INSTANCE.call(context) + subject = Kemal::ExceptionHandler.new(Kemal::Base.new) + subject.call(context) response.close io.rewind response = HTTP::Client::Response.from_io(io, decompress: false) @@ -20,19 +15,21 @@ describe "Kemal::ExceptionHandler" do end it "renders custom error" do - error 403 do - "403 error" - end - get "/" do |env| - env.response.status_code = 403 - end request = HTTP::Request.new("GET", "/") io = IO::Memory.new response = HTTP::Server::Response.new(io) context = HTTP::Server::Context.new(request, response) - context.app = Kemal.application - INSTANCE.next = Kemal::RouteHandler.new - INSTANCE.call(context) + app = Kemal::Base.new + app.error 403 do + "403 error" + end + app.get "/" do |env| + env.response.status_code = 403 + end + context.app = app + subject = Kemal::ExceptionHandler.new(app) + subject.next = Kemal::RouteHandler.new + subject.call(context) response.close io.rewind response = HTTP::Client::Response.from_io(io, decompress: false) @@ -42,19 +39,21 @@ describe "Kemal::ExceptionHandler" do end it "renders custom 500 error" do - error 500 do - "Something happened" - end - get "/" do |env| - env.response.status_code = 500 - end request = HTTP::Request.new("GET", "/") io = IO::Memory.new response = HTTP::Server::Response.new(io) context = HTTP::Server::Context.new(request, response) - context.app = Kemal.application - INSTANCE.next = Kemal::RouteHandler.new - INSTANCE.call(context) + app = Kemal::Base.new + app.error 500 do |env| + "Something happened" + end + app.get "/" do |env| + env.response.status_code = 500 + end + context.app = app + subject = Kemal::ExceptionHandler.new(app) + subject.next = Kemal::RouteHandler.new + subject.call(context) response.close io.rewind response = HTTP::Client::Response.from_io(io, decompress: false) @@ -64,20 +63,22 @@ describe "Kemal::ExceptionHandler" do end it "keeps the specified error Content-Type" do - error 500 do - "Something happened" - end - get "/" do |env| - env.response.content_type = "application/json" - env.response.status_code = 500 - end request = HTTP::Request.new("GET", "/") io = IO::Memory.new response = HTTP::Server::Response.new(io) context = HTTP::Server::Context.new(request, response) - context.app = Kemal.application - INSTANCE.next = Kemal::RouteHandler.new - INSTANCE.call(context) + app = Kemal::Base.new + app.error 500 do |env| + "Something happened" + end + app.get "/" do |env| + env.response.content_type = "application/json" + env.response.status_code = 500 + end + context.app = app + subject = Kemal::ExceptionHandler.new(app) + subject.next = Kemal::RouteHandler.new + subject.call(context) response.close io.rewind response = HTTP::Client::Response.from_io(io, decompress: false) @@ -87,20 +88,22 @@ describe "Kemal::ExceptionHandler" do end it "renders custom error with env and error" do - error 500 do |_, err| - err.message - end - get "/" do |env| - env.response.content_type = "application/json" - env.response.status_code = 500 - end request = HTTP::Request.new("GET", "/") io = IO::Memory.new response = HTTP::Server::Response.new(io) context = HTTP::Server::Context.new(request, response) - context.app = Kemal.application - INSTANCE.next = Kemal::RouteHandler.new - INSTANCE.call(context) + app = Kemal::Base.new + app.error 500 do |env, err| + err.message + end + app.get "/" do |env| + env.response.content_type = "application/json" + env.response.status_code = 500 + end + context.app = app + subject = Kemal::ExceptionHandler.new(Kemal::Base.new) + subject.next = Kemal::RouteHandler.new + subject.call(context) response.close io.rewind response = HTTP::Client::Response.from_io(io, decompress: false) diff --git a/spec/handler_spec.cr b/spec/handler_spec.cr index e256fa7..cd2a450 100644 --- a/spec/handler_spec.cr +++ b/spec/handler_spec.cr @@ -78,81 +78,88 @@ describe "Handler" do filter_middleware._add_route_filter("GET", "/", :before) do |env| env.response << " so" end - Kemal.application.add_filter_handler filter_middleware + app = Kemal::Base.new + app.add_filter_handler filter_middleware - add_handler CustomTestHandler.new + app.add_handler CustomTestHandler.new - get "/" do + app.get "/" do |env| " Great" end request = HTTP::Request.new("GET", "/") - client_response = call_request_on_app(request) + client_response = call_request_on_app(app, request) client_response.status_code.should eq(200) client_response.body.should eq("Kemal is so Great") end it "runs specified only_routes in middleware" do - get "/only" do + app = Kemal::Base.new + app.get "/only" do |env| "Get" end - add_handler OnlyHandler.new + app.add_handler OnlyHandler.new request = HTTP::Request.new("GET", "/only") - client_response = call_request_on_app(request) + client_response = call_request_on_app(app, request) client_response.body.should eq "OnlyGet" end it "doesn't run specified exclude_routes in middleware" do - get "/" do + app = Kemal::Base.new + app.get "/" do |env| "Get" end - get "/exclude" do + app.get "/exclude" do "Exclude" end - add_handler ExcludeHandler.new + app.add_handler ExcludeHandler.new request = HTTP::Request.new("GET", "/") - client_response = call_request_on_app(request) + client_response = call_request_on_app(app, request) client_response.body.should eq "ExcludeGet" end it "runs specified only_routes with method in middleware" do - post "/only" do + app = Kemal::Base.new + app.post "/only" do "Post" end - get "/only" do + app.get "/only" do "Get" end - add_handler PostOnlyHandler.new + app.add_handler PostOnlyHandler.new request = HTTP::Request.new("POST", "/only") - client_response = call_request_on_app(request) + client_response = call_request_on_app(app, request) client_response.body.should eq "OnlyPost" end it "doesn't run specified exclude_routes with method in middleware" do - post "/exclude" do + app = Kemal::Base.new + app.post "/exclude" do "Post" end - post "/only" do + app.post "/only" do "Post" end - add_handler PostOnlyHandler.new - add_handler PostExcludeHandler.new + app.add_handler PostOnlyHandler.new + app.add_handler PostExcludeHandler.new request = HTTP::Request.new("POST", "/only") - client_response = call_request_on_app(request) + client_response = call_request_on_app(app, request) client_response.body.should eq "OnlyExcludePost" end it "adds a handler at given position" do post_handler = PostOnlyHandler.new - add_handler post_handler, 1 - Kemal.application.setup - Kemal.application.handlers[1].should eq post_handler + app = Kemal::Base.new + app.add_handler post_handler, 1 + app.setup + app.handlers[1].should eq post_handler end it "assigns custom handlers" do post_only_handler = PostOnlyHandler.new post_exclude_handler = PostExcludeHandler.new - Kemal.application.handlers = [post_only_handler, post_exclude_handler] - Kemal.application.handlers.should eq [post_only_handler, post_exclude_handler] + app = Kemal::Base.new + app.handlers = [post_only_handler, post_exclude_handler] + app.handlers.should eq [post_only_handler, post_exclude_handler] end it "is able to use %w in macros" do diff --git a/spec/helpers_spec.cr b/spec/helpers_spec.cr index a7290d6..4efa80c 100644 --- a/spec/helpers_spec.cr +++ b/spec/helpers_spec.cr @@ -10,9 +10,10 @@ describe "Macros" do describe "#add_handler" do it "adds a custom handler" do - add_handler CustomTestHandler.new - Kemal.application.setup - Kemal.application.handlers.size.should eq 8 + app = Kemal::Application.new + app.add_handler CustomTestHandler.new + app.setup + app.handlers.size.should eq 8 end end @@ -23,7 +24,6 @@ describe "Macros" do end it "sets a custom logger" do - config = Kemal.config logger CustomLogHandler.new Kemal.application.logger.should be_a(CustomLogHandler) end @@ -31,32 +31,34 @@ describe "Macros" do describe "#halt" do it "can break block with halt macro" do - get "/non-breaking" do + app = Kemal::Base.new + app.get "/non-breaking" do |env| "hello" "world" end request = HTTP::Request.new("GET", "/non-breaking") - client_response = call_request_on_app(request) + client_response = call_request_on_app(app, request) client_response.status_code.should eq(200) client_response.body.should eq("world") - get "/breaking" do |env| + app.get "/breaking" do |env| halt env, 404, "hello" "world" end request = HTTP::Request.new("GET", "/breaking") - client_response = call_request_on_app(request) + client_response = call_request_on_app(app, request) client_response.status_code.should eq(404) client_response.body.should eq("hello") end it "can break block with halt macro using default values" do - get "/" do |env| + app = Kemal::Base.new + app.get "/" do |env| halt env "world" end request = HTTP::Request.new("GET", "/") - client_response = call_request_on_app(request) + client_response = call_request_on_app(app, request) client_response.status_code.should eq(200) client_response.body.should eq("") end @@ -64,7 +66,8 @@ describe "Macros" do describe "#headers" do it "can add headers" do - get "/headers" do |env| + app = Kemal::Base.new + app.get "/headers" do |env| env.response.headers.add "Content-Type", "image/png" headers env, { "Access-Control-Allow-Origin" => "*", @@ -72,7 +75,7 @@ describe "Macros" do } end request = HTTP::Request.new("GET", "/headers") - response = call_request_on_app(request) + response = call_request_on_app(app, request) response.headers["Access-Control-Allow-Origin"].should eq("*") response.headers["Content-Type"].should eq("text/plain") end @@ -80,36 +83,39 @@ describe "Macros" do describe "#send_file" do it "sends file with given path and default mime-type" do - get "/" do |env| + app = Kemal::Base.new + app.get "/" do |env| send_file env, "./spec/asset/hello.ecr" end request = HTTP::Request.new("GET", "/") - response = call_request_on_app(request) + response = call_request_on_app(app, request) response.status_code.should eq(200) response.headers["Content-Type"].should eq("application/octet-stream") response.headers["Content-Length"].should eq("18") end it "sends file with given path and given mime-type" do - get "/" do |env| + app = Kemal::Base.new + app.get "/" do |env| send_file env, "./spec/asset/hello.ecr", "image/jpeg" end request = HTTP::Request.new("GET", "/") - response = call_request_on_app(request) + response = call_request_on_app(app, request) response.status_code.should eq(200) response.headers["Content-Type"].should eq("image/jpeg") response.headers["Content-Length"].should eq("18") end it "sends file with binary stream" do - get "/" do |env| + app = Kemal::Base.new + app.get "/" do |env| send_file env, "Serdar".to_slice end request = HTTP::Request.new("GET", "/") - response = call_request_on_app(request) + response = call_request_on_app(app, request) response.status_code.should eq(200) response.headers["Content-Type"].should eq("application/octet-stream") response.headers["Content-Length"].should eq("6") diff --git a/spec/param_parser_spec.cr b/spec/param_parser_spec.cr index f85c94e..07d7604 100644 --- a/spec/param_parser_spec.cr +++ b/spec/param_parser_spec.cr @@ -1,4 +1,4 @@ -require "./spec_helper" +require "./dsl_helper" describe "ParamParser" do it "parses query params" do diff --git a/spec/route_handler_spec.cr b/spec/route_handler_spec.cr index a6db5e1..5b677b5 100644 --- a/spec/route_handler_spec.cr +++ b/spec/route_handler_spec.cr @@ -1,4 +1,4 @@ -require "./spec_helper" +require "./dsl_helper" describe "Kemal::RouteHandler" do it "routes" do diff --git a/spec/route_spec.cr b/spec/route_spec.cr index 7634d51..cb6139b 100644 --- a/spec/route_spec.cr +++ b/spec/route_spec.cr @@ -1,4 +1,4 @@ -require "./spec_helper" +require "./dsl_helper" describe "Route" do describe "match?" do diff --git a/spec/run_spec.cr b/spec/run_spec.cr index 915fab8..b711b68 100644 --- a/spec/run_spec.cr +++ b/spec/run_spec.cr @@ -1,4 +1,4 @@ -require "./spec_helper" +require "./dsl_helper" private def run(code) code = <<-CR diff --git a/spec/spec_helper.cr b/spec/spec_helper.cr index 2e62d70..bfa50b0 100644 --- a/spec/spec_helper.cr +++ b/spec/spec_helper.cr @@ -1,89 +1,24 @@ require "spec" -require "../src/**" -require "../src/kemal/base" -require "../src/kemal/dsl" +require "../src/kemal" -include Kemal - -class CustomLogHandler < Kemal::BaseLogHandler - def call(env) - call_next env - end - - def write(message) - end -end - -class TestContextStorageType - property id - @id = 1 - - def to_s - @id - end -end - -class AnotherContextStorageType - property name - @name = "kemal-context" -end - -add_context_storage_type(TestContextStorageType) -add_context_storage_type(AnotherContextStorageType) - -def create_request_and_return_io_and_context(handler, request) +def call_request_on_app(app, request) io = IO::Memory.new response = HTTP::Server::Response.new(io) context = HTTP::Server::Context.new(request, response) - context.app = Kemal.application - handler.call(context) - response.close - io.rewind - {io, context} -end - -def create_ws_request_and_return_io_and_context(handler, request) - io = IO::Memory.new - response = HTTP::Server::Response.new(io) - context = HTTP::Server::Context.new(request, response) - context.app = Kemal.application - begin - handler.call context - rescue IO::Error - # Raises because the IO::Memory is empty - end - io.rewind - {io, context} -end - -def call_request_on_app(request) - io = IO::Memory.new - response = HTTP::Server::Response.new(io) - context = HTTP::Server::Context.new(request, response) - main_handler = build_main_handler + main_handler = build_main_handler(app) main_handler.call context response.close io.rewind HTTP::Client::Response.from_io(io, decompress: false) end -def build_main_handler - Kemal.application.setup - main_handler = Kemal.application.handlers.first +def build_main_handler(app) + app.setup + main_handler = app.handlers.first current_handler = main_handler - Kemal.application.handlers.each_with_index do |handler, index| + app.handlers.each_with_index do |handler, index| current_handler.next = handler current_handler = handler end main_handler end - -Spec.before_each do - config = Kemal.config - config.env = "development" - config.logging = false -end - -Spec.after_each do - Kemal.application.clear -end diff --git a/spec/static_file_handler_spec.cr b/spec/static_file_handler_spec.cr index 54293b4..bbb0a24 100644 --- a/spec/static_file_handler_spec.cr +++ b/spec/static_file_handler_spec.cr @@ -1,17 +1,22 @@ require "./spec_helper" -private def handle(request, fallthrough = true) +private def handle(request, config = default_config, fallthrough = true) io = IO::Memory.new response = HTTP::Server::Response.new(io) context = HTTP::Server::Context.new(request, response) - context.app = Kemal.application - handler = Kemal::StaticFileHandler.new "#{__DIR__}/static", fallthrough + handler = Kemal::StaticFileHandler.new config, fallthrough handler.call context response.close io.rewind HTTP::Client::Response.from_io(io) end +private def default_config + Kemal::Config.new.tap do |config| + config.public_folder = "#{__DIR__}/static" + end +end + describe Kemal::StaticFileHandler do file = File.open "#{__DIR__}/static/dir/test.txt" file_size = file.size @@ -37,38 +42,43 @@ describe Kemal::StaticFileHandler do end it "should not list directory's entries" do - serve_static({"gzip" => true, "dir_listing" => false}) - response = handle HTTP::Request.new("GET", "/dir/") + config = default_config + config.serve_static = {"gzip" => true, "dir_listing" => false} + response = handle HTTP::Request.new("GET", "/dir/"), config response.status_code.should eq(404) end it "should list directory's entries when config is set" do - serve_static({"gzip" => true, "dir_listing" => true}) - response = handle HTTP::Request.new("GET", "/dir/") + config = default_config + config.serve_static = {"gzip" => true, "dir_listing" => true} + response = handle HTTP::Request.new("GET", "/dir/"), config response.status_code.should eq(200) response.body.should match(/test.txt/) end it "should gzip a file if config is true, headers accept gzip and file is > 880 bytes" do - serve_static({"gzip" => true, "dir_listing" => true}) + config = default_config + config.serve_static = {"gzip" => true, "dir_listing" => true} headers = HTTP::Headers{"Accept-Encoding" => "gzip, deflate, sdch, br"} - response = handle HTTP::Request.new("GET", "/dir/bigger.txt", headers) + response = handle HTTP::Request.new("GET", "/dir/bigger.txt", headers), config response.status_code.should eq(200) response.headers["Content-Encoding"].should eq "gzip" end it "should not gzip a file if config is true, headers accept gzip and file is < 880 bytes" do - serve_static({"gzip" => true, "dir_listing" => true}) + config = default_config + config.serve_static = {"gzip" => true, "dir_listing" => true} headers = HTTP::Headers{"Accept-Encoding" => "gzip, deflate, sdch, br"} - response = handle HTTP::Request.new("GET", "/dir/test.txt", headers) + response = handle HTTP::Request.new("GET", "/dir/test.txt", headers), config response.status_code.should eq(200) response.headers["Content-Encoding"]?.should be_nil end it "should not gzip a file if config is false, headers accept gzip and file is > 880 bytes" do - serve_static({"gzip" => false, "dir_listing" => true}) + config = default_config + config.serve_static = {"gzip" => false, "dir_listing" => true} headers = HTTP::Headers{"Accept-Encoding" => "gzip, deflate, sdch, br"} - response = handle HTTP::Request.new("GET", "/dir/bigger.txt", headers) + response = handle HTTP::Request.new("GET", "/dir/bigger.txt", headers), config response.status_code.should eq(200) response.headers["Content-Encoding"]?.should be_nil end @@ -97,7 +107,7 @@ describe Kemal::StaticFileHandler do %w(POST PUT DELETE).each do |method| response = handle HTTP::Request.new(method, "/dir/test.txt") response.status_code.should eq(404) - response = handle HTTP::Request.new(method, "/dir/test.txt"), false + response = handle HTTP::Request.new(method, "/dir/test.txt"), fallthrough: false response.status_code.should eq(405) response.headers["Allow"].should eq("GET, HEAD") end @@ -133,22 +143,21 @@ describe Kemal::StaticFileHandler do end it "should handle setting custom headers" do - headers = Proc(HTTP::Server::Response, String, File::Info, Void).new do |response, path, stat| + config = default_config + config.static_headers = Proc(HTTP::Server::Response, String, File::Info, Void).new do |response, path, stat| if path =~ /\.html$/ response.headers.add("Access-Control-Allow-Origin", "*") end response.headers.add("Content-Size", stat.size.to_s) end - static_headers(&headers) - - response = handle HTTP::Request.new("GET", "/dir/test.txt") + response = handle HTTP::Request.new("GET", "/dir/test.txt"), config response.headers.has_key?("Access-Control-Allow-Origin").should be_false response.headers["Content-Size"].should eq( File.info("#{__DIR__}/static/dir/test.txt").size.to_s ) - response = handle HTTP::Request.new("GET", "/dir/index.html") + response = handle HTTP::Request.new("GET", "/dir/index.html"), config response.headers["Access-Control-Allow-Origin"].should eq("*") end end diff --git a/spec/view_spec.cr b/spec/view_spec.cr index d09f4de..f6e6cf7 100644 --- a/spec/view_spec.cr +++ b/spec/view_spec.cr @@ -1,4 +1,4 @@ -require "./spec_helper" +require "./dsl_helper" macro render_with_base_and_layout(filename) render "spec/asset/#{{{filename}}}", "spec/asset/layout.ecr" @@ -6,56 +6,61 @@ end describe "Views" do it "renders file" do - get "/view/:name" do |env| + app = Kemal::Base.new + app.get "/view/:name" do |env| name = env.params.url["name"] render "spec/asset/hello.ecr" end request = HTTP::Request.new("GET", "/view/world") - client_response = call_request_on_app(request) + client_response = call_request_on_app(app, request) client_response.body.should contain("Hello world") end it "renders file with dynamic variables" do - get "/view/:name" do |env| + app = Kemal::Base.new + app.get "/view/:name" do |env| name = env.params.url["name"] render_with_base_and_layout "hello.ecr" end request = HTTP::Request.new("GET", "/view/world") - client_response = call_request_on_app(request) + client_response = call_request_on_app(app, request) client_response.body.should contain("Hello world") end it "renders layout" do - get "/view/:name" do |env| + app = Kemal::Base.new + app.get "/view/:name" do |env| name = env.params.url["name"] render "spec/asset/hello.ecr", "spec/asset/layout.ecr" end request = HTTP::Request.new("GET", "/view/world") - client_response = call_request_on_app(request) + client_response = call_request_on_app(app, request) client_response.body.should contain("Hello world") end it "renders layout with variables" do - get "/view/:name" do |env| + app = Kemal::Base.new + app.get "/view/:name" do |env| name = env.params.url["name"] var1 = "serdar" var2 = "kemal" render "spec/asset/hello_with_content_for.ecr", "spec/asset/layout_with_yield_and_vars.ecr" end request = HTTP::Request.new("GET", "/view/world") - client_response = call_request_on_app(request) + client_response = call_request_on_app(app, request) client_response.body.should contain("Hello world") client_response.body.should contain("serdar") client_response.body.should contain("kemal") end it "renders layout with content_for" do - get "/view/:name" do |env| + app = Kemal::Base.new + app.get "/view/:name" do |env| name = env.params.url["name"] render "spec/asset/hello_with_content_for.ecr", "spec/asset/layout_with_yield.ecr" end request = HTTP::Request.new("GET", "/view/world") - client_response = call_request_on_app(request) + client_response = call_request_on_app(app, request) client_response.body.should contain("Hello world") client_response.body.should contain("

Hello from otherside

") end diff --git a/spec/websocket_handler_spec.cr b/spec/websocket_handler_spec.cr index ab3d58e..cbcb34e 100644 --- a/spec/websocket_handler_spec.cr +++ b/spec/websocket_handler_spec.cr @@ -1,10 +1,24 @@ require "./spec_helper" +private def create_ws_request_and_return_io(handler, request, app) + io = IO::Memory.new + response = HTTP::Server::Response.new(io) + context = HTTP::Server::Context.new(request, response) + context.app = app + begin + handler.call context + rescue IO::Error + # Raises because the IO::Memory is empty + end + io +end + describe "Kemal::WebSocketHandler" do it "doesn't match on wrong route" do + app = Kemal::Base.new handler = Kemal::WebSocketHandler.new handler.next = Kemal::RouteHandler.new - ws "/" { } + app.ws "/" { } headers = HTTP::Headers{ "Upgrade" => "websocket", "Connection" => "Upgrade", @@ -14,7 +28,7 @@ describe "Kemal::WebSocketHandler" do io = IO::Memory.new response = HTTP::Server::Response.new(io) context = HTTP::Server::Context.new(request, response) - context.app = Kemal.application + context.app = app expect_raises(Kemal::Exceptions::RouteNotFound) do handler.call context @@ -22,9 +36,10 @@ describe "Kemal::WebSocketHandler" do end it "matches on given route" do + app = Kemal::Base.new handler = Kemal::WebSocketHandler.new - ws "/" { |socket, context| socket.send("Match") } - ws "/no_match" { |socket, context| socket.send "No Match" } + app.ws "/" { |socket, context| socket.send("Match") } + app.ws "/no_match" { |socket, context| socket.send "No Match" } headers = HTTP::Headers{ "Upgrade" => "websocket", "Connection" => "Upgrade", @@ -33,13 +48,14 @@ describe "Kemal::WebSocketHandler" do } request = HTTP::Request.new("GET", "/", headers) - io_with_context = create_ws_request_and_return_io_and_context(handler, request)[0] - io_with_context.to_s.should eq("HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n\r\n\x81\u0005Match") + io_with_context = create_ws_request_and_return_io(handler, request, app) + io_with_context.to_s.should eq("HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-Websocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n\r\n\x81\u0005Match") end it "fetches named url parameters" do + app = Kemal::Base.new handler = Kemal::WebSocketHandler.new - ws "/:id" { |s, c| c.params.url["id"] } + app.ws "/:id" { |s, c| c.params.url["id"] } headers = HTTP::Headers{ "Upgrade" => "websocket", "Connection" => "Upgrade", @@ -47,20 +63,21 @@ describe "Kemal::WebSocketHandler" do "Sec-WebSocket-Version" => "13", } request = HTTP::Request.new("GET", "/1234", headers) - io_with_context = create_ws_request_and_return_io_and_context(handler, request)[0] - io_with_context.to_s.should eq("HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n\r\n") + io_with_context = create_ws_request_and_return_io(handler, request, app) + io_with_context.to_s.should eq("HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-Websocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n\r\n") end it "matches correct verb" do + app = Kemal::Base.new handler = Kemal::WebSocketHandler.new handler.next = Kemal::RouteHandler.new - ws "/" { } - get "/" { "get" } + app.ws "/" { } + app.get "/" { "get" } request = HTTP::Request.new("GET", "/") io = IO::Memory.new response = HTTP::Server::Response.new(io) context = HTTP::Server::Context.new(request, response) - context.app = Kemal.application + context.app = app handler.call(context) response.close io.rewind diff --git a/src/kemal/base.cr b/src/kemal/base.cr index f33e1bc..649d6c1 100644 --- a/src/kemal/base.cr +++ b/src/kemal/base.cr @@ -99,4 +99,8 @@ class Kemal::Base raise "Kemal is already stopped." end end + + def log(message) + logger.write "#{message}\n" + end end diff --git a/src/kemal/base/builder.cr b/src/kemal/base/builder.cr index f330234..e4aaad1 100644 --- a/src/kemal/base/builder.cr +++ b/src/kemal/base/builder.cr @@ -71,7 +71,7 @@ class Kemal::Base private def setup_error_handler if @config.always_rescue? - error_handler = @error_handler ||= Kemal::ExceptionHandler.new + error_handler = @error_handler ||= Kemal::ExceptionHandler.new(self) @handlers.insert(@handler_position, error_handler) @handler_position += 1 end @@ -79,7 +79,7 @@ class Kemal::Base private def setup_static_file_handler if @config.serve_static.is_a?(Hash) - @handlers.insert(@handler_position, Kemal::StaticFileHandler.new(@config.public_folder)) + @handlers.insert(@handler_position, Kemal::StaticFileHandler.new(@config)) @handler_position += 1 end end diff --git a/src/kemal/config.cr b/src/kemal/config.cr index 58f4979..0af2101 100644 --- a/src/kemal/config.cr +++ b/src/kemal/config.cr @@ -57,6 +57,10 @@ module Kemal def extra_options(&@extra_options : OptionParser ->) end + def serve_static?(key) + (h = @serve_static).is_a?(Hash) && h[key]? == true + end + # Create a config with default values def self.default new diff --git a/src/kemal/exception_handler.cr b/src/kemal/exception_handler.cr index e1d7168..32da24b 100644 --- a/src/kemal/exception_handler.cr +++ b/src/kemal/exception_handler.cr @@ -3,6 +3,13 @@ module Kemal class ExceptionHandler include HTTP::Handler + getter app : Kemal::Base + + def initialize(@app) + end + + delegate log, to: app + def call(context : HTTP::Server::Context) begin call_next(context) @@ -14,7 +21,7 @@ module Kemal log("Exception: #{ex.inspect_with_backtrace}") return call_exception_with_status_code(context, ex, 500) if context.app.error_handlers.has_key?(500) verbosity = context.app.config.env == "production" ? false : true - return render_500(context, ex.inspect_with_backtrace, verbosity) + return app.render_500(context, ex.inspect_with_backtrace, verbosity) end end diff --git a/src/kemal/helpers/file_helpers.cr b/src/kemal/helpers/file_helpers.cr index c30d0e9..375439c 100644 --- a/src/kemal/helpers/file_helpers.cr +++ b/src/kemal/helpers/file_helpers.cr @@ -1,8 +1,5 @@ module Kemal::FileHelpers - def log(message) - logger.write "#{message}\n" - end - + extend self # Send a file with given path and base the mime-type on the file extension # or default `application/octet-stream` mime_type. # @@ -15,8 +12,7 @@ module Kemal::FileHelpers # ``` # send_file env, "./path/to/file", "image/jpeg" # ``` - def send_file(env : HTTP::Server::Context, path : String, mime_type : String? = nil) - config = env.app.config + def send_file(env : HTTP::Server::Context, path : String, config : Kemal::Config, mime_type : String? = nil) file_path = File.expand_path(path, Dir.current) mime_type ||= Kemal::Utils.mime_type(file_path) env.response.content_type = mime_type @@ -28,17 +24,18 @@ module Kemal::FileHelpers filestat = File.stat(file_path) config.static_headers.try(&.call(env.response, file_path, filestat)) + gzip = config.serve_static?("gzip") File.open(file_path) do |file| if env.request.method == "GET" && env.request.headers.has_key?("Range") next multipart(file, env) end - if request_headers.includes_word?("Accept-Encoding", "gzip") && config.serve_static?("gzip") && filesize > minsize && Kemal::Utils.zip_types(file_path) + if request_headers.includes_word?("Accept-Encoding", "gzip") && gzip && filesize > minsize && Kemal::Utils.zip_types(file_path) env.response.headers["Content-Encoding"] = "gzip" Gzip::Writer.open(env.response) do |deflate| IO.copy(file, deflate) end - elsif request_headers.includes_word?("Accept-Encoding", "deflate") && config.serve_static?("gzip") && filesize > minsize && Kemal::Utils.zip_types(file_path) + elsif request_headers.includes_word?("Accept-Encoding", "deflate") && gzip && filesize > minsize && Kemal::Utils.zip_types(file_path) env.response.headers["Content-Encoding"] = "deflate" Flate::Writer.open(env.response) do |deflate| IO.copy(file, deflate) @@ -51,6 +48,10 @@ module Kemal::FileHelpers return end + def send_file(env, path : String, mime_type : String? = nil) + send_file(env, path, env.app.config, mime_type) + end + private def multipart(file, env : HTTP::Server::Context) # See http://httpwg.org/specs/rfc7233.html fileb = file.size diff --git a/src/kemal/static_file_handler.cr b/src/kemal/static_file_handler.cr index 50d1574..fc6446c 100644 --- a/src/kemal/static_file_handler.cr +++ b/src/kemal/static_file_handler.cr @@ -4,6 +4,12 @@ module Kemal class StaticFileHandler < HTTP::StaticFileHandler + getter config : Kemal::Config + + def initialize(@config, fallthrough = true) + super(@config.public_folder, fallthrough) + end + def call(context : HTTP::Server::Context) return call_next(context) if context.request.path.not_nil! == "/" @@ -19,7 +25,6 @@ module Kemal return end - config = Kemal.config.serve_static original_path = context.request.path.not_nil! request_path = URI.unescape(original_path) @@ -48,7 +53,7 @@ module Kemal end if Dir.exists?(file_path) - if config.is_a?(Hash) && config["dir_listing"] == true + if @config.serve_static?("dir_listing") context.response.content_type = "text/html" directory_listing(context.response, request_path, file_path) else @@ -62,7 +67,8 @@ module Kemal context.response.status_code = 304 return end - send_file(context, file_path) + + FileHelpers.send_file(context, file_path, config) else call_next(context) end