diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index f52233b..3452e4a 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,6 +1,6 @@ # These are supported funding model platforms -github: sdogruyol +github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] patreon: sdogruyol open_collective: # Replace with a single Open Collective username ko_fi: # Replace with a single Ko-fi username diff --git a/CHANGELOG.md b/CHANGELOG.md index 3bc639b..5b0f83c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,24 +1,3 @@ -# 1.2.0 (07-07-2022) - -- Crystal 1.5.0 support :tada: -- Eliminated several seconds of delay when loading big mp4 file. Thanks @Athlon64 :pray: -- Fix content_for failing to capture the correct block input [#639](https://github.com/kemalcr/kemal/pull/639). Thanks @sdogruyol :pray: -- Closes response by default in HTTP::Server::Context#redirect [#641](https://github.com/kemalcr/kemal/pull/641). Thanks @cyangle :pray: -- Enable option for index.html to be a directories default [#640](https://github.com/kemalcr/kemal/pull/640). Thanks @ukd1 :pray: - -You can enable it via - -```crystal - serve_static({"dir_index" => true}) -``` - -# 1.1.2 (24-02-2022) - -- Fix content rendering [#631](https://github.com/kemalcr/kemal/pull/631). Thanks @matthewmcgarvey :pray: - -# 1.1.1 (22-02-2022) - -- Ignore HTTP::Server::Response patching for crystal >= 1.3.0 [#628](https://github.com/kemalcr/kemal/pull/628). Thanks @SamantazFox :pray: # 1.1.0 (02-09-2021) - You can now set your own application name for startup message [#606](https://github.com/kemalcr/kemal/pull/606). Thanks @aravindavk :pray: diff --git a/shard.yml b/shard.yml index 69a311a..72011fd 100644 --- a/shard.yml +++ b/shard.yml @@ -1,5 +1,5 @@ name: kemal -version: 1.2.0 +version: 1.1.0 authors: - Serdar Dogruyol @@ -15,7 +15,7 @@ dependencies: development_dependencies: ameba: github: crystal-ameba/ameba - version: ~> 1.0 + version: ~> 0.14.0 crystal: ">= 0.36.0" diff --git a/spec/asset/hello_with_content_for.ecr b/spec/asset/hello_with_content_for.ecr index b5460f9..149b294 100644 --- a/spec/asset/hello_with_content_for.ecr +++ b/spec/asset/hello_with_content_for.ecr @@ -1,5 +1,5 @@ Hello <%= name %> -<% content_for "meta" do %> -Kemal Spec +<% content_for "custom" do %> +

Hello from otherside

<% end %> \ No newline at end of file diff --git a/spec/asset/layout_with_yield.ecr b/spec/asset/layout_with_yield.ecr index 3710c4a..f6cd673 100644 --- a/spec/asset/layout_with_yield.ecr +++ b/spec/asset/layout_with_yield.ecr @@ -1,8 +1,6 @@ - - <%= yield_content "meta" %> - <%= content %> + <%= yield_content "custom" %> \ No newline at end of file diff --git a/spec/asset/layout_with_yield_and_vars.ecr b/spec/asset/layout_with_yield_and_vars.ecr index d2a8a35..3a82a7a 100644 --- a/spec/asset/layout_with_yield_and_vars.ecr +++ b/spec/asset/layout_with_yield_and_vars.ecr @@ -1,10 +1,8 @@ - - <%= yield_content "meta" %> - <%= content %> - <%= var1 %> - <%= var2 %> + <%= yield_content "custom" %> + <%= var1 %> + <%= var2 %> \ No newline at end of file diff --git a/spec/route_handler_spec.cr b/spec/route_handler_spec.cr index e3046e3..93cf0f0 100644 --- a/spec/route_handler_spec.cr +++ b/spec/route_handler_spec.cr @@ -143,40 +143,4 @@ describe "Kemal::RouteHandler" do client_response.body.should eq("Redirecting to /login") client_response.headers.has_key?("Location").should eq(true) end - - it "redirects and closes response in before filter" do - filter_handler = Kemal::FilterHandler.new - filter_handler._add_route_filter("GET", "/", :before) do |env| - env.redirect "/login" - end - Kemal.config.add_filter_handler(filter_handler) - - get "/" do - "home page" - end - - request = HTTP::Request.new("GET", "/") - client_response = call_request_on_app(request) - client_response.status_code.should eq(302) - client_response.body.should eq("") - client_response.headers.has_key?("Location").should eq(true) - end - - it "redirects in before filter without closing response" do - filter_handler = Kemal::FilterHandler.new - filter_handler._add_route_filter("GET", "/", :before) do |env| - env.redirect "/login", close: false - end - Kemal.config.add_filter_handler(filter_handler) - - get "/" do - "home page" - end - - request = HTTP::Request.new("GET", "/") - client_response = call_request_on_app(request) - client_response.status_code.should eq(302) - client_response.body.should eq("home page") - client_response.headers.has_key?("Location").should eq(true) - end end diff --git a/spec/spec_helper.cr b/spec/spec_helper.cr index 0065848..803aacf 100644 --- a/spec/spec_helper.cr +++ b/spec/spec_helper.cr @@ -4,8 +4,8 @@ require "../src/*" include Kemal class CustomLogHandler < Kemal::BaseLogHandler - def call(context) - call_next(context) + def call(env) + call_next env end def write(message) diff --git a/spec/static_file_handler_spec.cr b/spec/static_file_handler_spec.cr index 309e10f..e9307f6 100644 --- a/spec/static_file_handler_spec.cr +++ b/spec/static_file_handler_spec.cr @@ -23,15 +23,6 @@ describe Kemal::StaticFileHandler do response.body.should eq(File.read("#{__DIR__}/static/dir/test.txt")) end - it "should serve the 'index.html' file when a directory is requested and index serving is enabled" do - serve_static({"dir_index" => true}) - response = handle HTTP::Request.new("GET", "/dir/") - response.status_code.should eq(200) - response.headers["Content-Type"].should eq "text/html" - response.headers["Etag"].should contain "W/\"" - response.body.should eq(File.read("#{__DIR__}/static/dir/index.html")) - end - it "should respond with 304 if file has not changed" do response = handle HTTP::Request.new("GET", "/dir/test.txt") response.status_code.should eq(200) diff --git a/spec/view_spec.cr b/spec/view_spec.cr index 79b8768..4705233 100644 --- a/spec/view_spec.cr +++ b/spec/view_spec.cr @@ -22,7 +22,7 @@ describe "Views" do end request = HTTP::Request.new("GET", "/view/world") client_response = call_request_on_app(request) - client_response.body.strip.should eq("Hello world\n") + client_response.body.should contain("Hello world") end it "renders layout" do @@ -56,17 +56,7 @@ describe "Views" do end request = HTTP::Request.new("GET", "/view/world") client_response = call_request_on_app(request) - client_response.body.scan("Hello world").size.should eq(1) - client_response.body.should contain("Kemal Spec") - end - - it "does not render content_for that was not yielded" do - get "/view/:name" do |env| - name = env.params.url["name"] - render "#{__DIR__}/asset/hello_with_content_for.ecr", "#{__DIR__}/asset/layout.ecr" - end - request = HTTP::Request.new("GET", "/view/world") - client_response = call_request_on_app(request) - client_response.body.should_not contain("

Hello from otherside

") + client_response.body.should contain("Hello world") + client_response.body.should contain("

Hello from otherside

") end end diff --git a/src/kemal.cr b/src/kemal.cr index 9222fe6..6f1a564 100644 --- a/src/kemal.cr +++ b/src/kemal.cr @@ -7,18 +7,18 @@ require "./kemal/helpers/*" module Kemal # Overload of `self.run` with the default startup logging. - def self.run(port : Int32?, args = ARGV, trap_signal : Bool = true) - self.run(port, args, trap_signal) { } + def self.run(port : Int32?, args = ARGV) + self.run(port, args) { } end # Overload of `self.run` without port. - def self.run(args = ARGV, trap_signal : Bool = true) - self.run(nil, args: args, trap_signal: trap_signal) + def self.run(args = ARGV) + self.run(nil, args: args) end # Overload of `self.run` to allow just a block. def self.run(args = ARGV, &block) - self.run(nil, args: args, trap_signal: true, &block) + self.run(nil, args: args, &block) end # The command to run a `Kemal` application. @@ -27,7 +27,7 @@ module Kemal # # To use custom command line arguments, set args to nil # - def self.run(port : Int32? = nil, args = ARGV, trap_signal : Bool = true, &block) + def self.run(port : Int32? = nil, args = ARGV, &block) Kemal::CLI.new args config = Kemal.config config.setup @@ -36,7 +36,7 @@ module Kemal # Test environment doesn't need to have signal trap and logging. if config.env != "test" setup_404 - setup_trap_signal if trap_signal + setup_trap_signal end server = config.server ||= HTTP::Server.new(config.handlers) diff --git a/src/kemal/config.cr b/src/kemal/config.cr index 23372dd..4e62d8d 100644 --- a/src/kemal/config.cr +++ b/src/kemal/config.cr @@ -31,7 +31,7 @@ module Kemal @host_binding = "0.0.0.0" @port = 3000 @env = ENV["KEMAL_ENV"]? || "development" - @serve_static = {"dir_listing" => false, "gzip" => true, "dir_index" => false} + @serve_static = {"dir_listing" => false, "gzip" => true} @public_folder = "./public" @logging = true @logger = nil diff --git a/src/kemal/ext/context.cr b/src/kemal/ext/context.cr index 70787b6..c2e51c6 100644 --- a/src/kemal/ext/context.cr +++ b/src/kemal/ext/context.cr @@ -17,11 +17,10 @@ class HTTP::Server @params ||= Kemal::ParamParser.new(@request, route_lookup.params) end - def redirect(url : String, status_code : Int32 = 302, *, body : String? = nil, close : Bool = true) + def redirect(url : String, status_code : Int32 = 302, *, body : String? = nil) @response.headers.add "Location", url @response.status_code = status_code @response.print(body) if body - @response.close if close end def route diff --git a/src/kemal/ext/response.cr b/src/kemal/ext/response.cr index 3645bd5..1d11dd1 100644 --- a/src/kemal/ext/response.cr +++ b/src/kemal/ext/response.cr @@ -1,7 +1,3 @@ -# This override collides with the new stdlib of Crystal 1.3 -# See https://github.com/kemalcr/kemal/issues/627 for more details -{{ skip_file if compare_versions(Crystal::VERSION, "1.3.0") >= 0 }} - class HTTP::Server::Response class Output def close diff --git a/src/kemal/handler.cr b/src/kemal/handler.cr index 6016ba7..bb59238 100644 --- a/src/kemal/handler.cr +++ b/src/kemal/handler.cr @@ -27,8 +27,8 @@ module Kemal end end - def call(context : HTTP::Server::Context) - call_next(context) + def call(env : HTTP::Server::Context) + call_next(env) end # Processes the path based on `only` paths which is a `Array(String)`. diff --git a/src/kemal/helpers/helpers.cr b/src/kemal/helpers/helpers.cr index 11a6b62..bf212a2 100644 --- a/src/kemal/helpers/helpers.cr +++ b/src/kemal/helpers/helpers.cr @@ -216,7 +216,20 @@ private def multipart(file, env : HTTP::Server::Context) env.response.headers["Accept-Ranges"] = "bytes" env.response.headers["Content-Range"] = "bytes #{startb}-#{endb}/#{fileb}" # MUST - file.seek(startb) + if startb > 1024 + skipped = 0_i64 + # file.skip only accepts values less or equal to 1024 (buffer size, undocumented) + until (increase_skipped = skipped + 1024_i64) > startb + file.skip(1024) + skipped = increase_skipped + end + if (skipped_minus_startb = skipped - startb) > 0 + file.skip skipped_minus_startb + end + else + file.skip(startb) + end + IO.copy(file, env.response, content_length) else env.response.content_length = fileb diff --git a/src/kemal/helpers/macros.cr b/src/kemal/helpers/macros.cr index 71ba2be..41c8793 100644 --- a/src/kemal/helpers/macros.cr +++ b/src/kemal/helpers/macros.cr @@ -1,4 +1,4 @@ -CONTENT_FOR_BLOCKS = Hash(String, Tuple(String, Proc(Nil))).new +CONTENT_FOR_BLOCKS = Hash(String, Tuple(String, Proc(String))).new # `content_for` is a set of helpers that allows you to capture # blocks inside views to be rendered later during the request. The most @@ -34,7 +34,13 @@ CONTENT_FOR_BLOCKS = Hash(String, Tuple(String, Proc(Nil))).new # layout, inside the tag, and each view can call `content_for` # setting the appropriate set of tags that should be added to the layout. macro content_for(key, file = __FILE__) - CONTENT_FOR_BLOCKS[{{key}}] = Tuple.new {{file}}, ->() { {{ yield }} } + %proc = ->() { + __view_io__ = IO::Memory.new + {{ yield }} + __view_io__.to_s + } + + CONTENT_FOR_BLOCKS[{{key}}] = Tuple.new {{file}}, %proc nil end @@ -43,14 +49,7 @@ macro yield_content(key) if CONTENT_FOR_BLOCKS.has_key?({{key}}) __caller_filename__ = CONTENT_FOR_BLOCKS[{{key}}][0] %proc = CONTENT_FOR_BLOCKS[{{key}}][1] - - if __content_filename__ == __caller_filename__ - %old_content_io, content_io = content_io, IO::Memory.new - %proc.call - %result = content_io.to_s - content_io = %old_content_io - %result - end + %proc.call if __content_filename__ == __caller_filename__ end end @@ -61,12 +60,10 @@ end # ``` macro render(filename, layout) __content_filename__ = {{filename}} - content_io = IO::Memory.new - ECR.embed {{filename}}, content_io - content = content_io.to_s - layout_io = IO::Memory.new - ECR.embed {{layout}}, layout_io - layout_io.to_s + io = IO::Memory.new + content = ECR.embed {{filename}}, io + ECR.embed {{layout}}, io + io.to_s end # Render view with the given filename. diff --git a/src/kemal/static_file_handler.cr b/src/kemal/static_file_handler.cr index dbeca28..109e971 100644 --- a/src/kemal/static_file_handler.cr +++ b/src/kemal/static_file_handler.cr @@ -36,7 +36,7 @@ module Kemal end file_path = File.join(@public_dir, expanded_path) - is_dir = Dir.exists?(file_path) + is_dir = Dir.exists? file_path if request_path != expanded_path redirect_to context, expanded_path @@ -44,19 +44,8 @@ module Kemal redirect_to context, expanded_path + '/' end - if is_dir - if config.is_a?(Hash) && config.fetch("dir_index", false) && File.exists?(File.join(file_path, "index.html")) - file_path = File.join(@public_dir, expanded_path, "index.html") - - last_modified = modification_time(file_path) - add_cache_headers(context.response.headers, last_modified) - - if cache_request?(context, last_modified) - context.response.status_code = 304 - return - end - send_file(context, file_path) - elsif config.is_a?(Hash) && config.fetch("dir_listing", false) + if Dir.exists?(file_path) + if config.is_a?(Hash) && config["dir_listing"] == true context.response.content_type = "text/html" directory_listing(context.response, request_path, file_path) else @@ -75,9 +64,5 @@ module Kemal call_next(context) end end - - private def modification_time(file_path) - File.info(file_path).modification_time - end end end