From 93521b712031d8a01004fdf21ddfc75784f83c91 Mon Sep 17 00:00:00 2001 From: Sijawusz Pur Rahnama Date: Mon, 15 Aug 2022 11:00:32 +0200 Subject: [PATCH 01/33] Bump `exception_page` shard to v0.3.0 (#645) --- shard.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shard.yml b/shard.yml index 69a311a..2f10371 100644 --- a/shard.yml +++ b/shard.yml @@ -10,7 +10,7 @@ dependencies: version: ~> 0.4.0 exception_page: github: crystal-loot/exception_page - version: ~> 0.2.0 + version: ~> 0.3.0 development_dependencies: ameba: From c8f857dff35a07eacfc3f4d924d2bc056ca31aa7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Serdar=20Dogruyol=20-=20Sedo=20=E3=82=BB=E3=83=89?= <990485+sdogruyol@users.noreply.github.com> Date: Thu, 15 Sep 2022 11:52:28 +0300 Subject: [PATCH 02/33] Omitting filters fix for lowercase methods requests (#647) --- spec/filters_spec.cr | 37 +++++++++++++++++++++++++++++++++++++ spec/spec_helper.cr | 1 + src/kemal/filter_handler.cr | 1 + src/kemal/handler.cr | 12 +++++------- src/kemal/route_handler.cr | 2 +- 5 files changed, 45 insertions(+), 8 deletions(-) create mode 100644 spec/filters_spec.cr diff --git a/spec/filters_spec.cr b/spec/filters_spec.cr new file mode 100644 index 0000000..e74878b --- /dev/null +++ b/spec/filters_spec.cr @@ -0,0 +1,37 @@ +require "./spec_helper" + +describe "Kemal::FilterHandler" do + it "handles with upcased 'POST'" do + filter_handler = Kemal::FilterHandler.new + filter_handler._add_route_filter("POST", "*", :before) do |env| + env.set "sensitive", "1" + end + Kemal.config.add_filter_handler(filter_handler) + + post "/sensitive_post" do |env| + env.get "sensitive" + end + + request = HTTP::Request.new("POST", "/sensitive_post") + client_response = call_request_on_app(request) + client_response.status_code.should eq(200) + client_response.body.should eq("1") + end + + it "handles with downcased 'post'" do + filter_handler = Kemal::FilterHandler.new + filter_handler._add_route_filter("POST", "*", :before) do |env| + env.set "sensitive", "1" + end + Kemal.config.add_filter_handler(filter_handler) + + post "/sensitive_post" do + "sensitive" + end + + request = HTTP::Request.new("post", "/sensitive_post") + client_response = call_request_on_app(request) + client_response.status_code.should eq(200) + client_response.body.should eq("") + end +end diff --git a/spec/spec_helper.cr b/spec/spec_helper.cr index 0065848..6509e1e 100644 --- a/spec/spec_helper.cr +++ b/spec/spec_helper.cr @@ -85,6 +85,7 @@ end Spec.after_each do Kemal.config.clear + Kemal::FilterHandler::INSTANCE.tree = Radix::Tree(Array(Kemal::FilterHandler::FilterBlock)).new Kemal::RouteHandler::INSTANCE.routes = Radix::Tree(Route).new Kemal::RouteHandler::INSTANCE.cached_routes = Hash(String, Radix::Result(Route)).new Kemal::WebSocketHandler::INSTANCE.routes = Radix::Tree(WebSocket).new diff --git a/src/kemal/filter_handler.cr b/src/kemal/filter_handler.cr index 298ce26..5bf9fd6 100644 --- a/src/kemal/filter_handler.cr +++ b/src/kemal/filter_handler.cr @@ -3,6 +3,7 @@ module Kemal class FilterHandler include HTTP::Handler INSTANCE = new + property tree # This middleware is lazily instantiated and added to the handlers as soon as a call to `after_X` or `before_X` is made. def initialize diff --git a/src/kemal/handler.cr b/src/kemal/handler.cr index 6016ba7..f87e60a 100644 --- a/src/kemal/handler.cr +++ b/src/kemal/handler.cr @@ -11,19 +11,17 @@ module Kemal macro only(paths, method = "GET") class_name = {{@type.name}} - method_downcase = {{method.downcase}} - class_name_method = "#{class_name}/#{method_downcase}" + class_name_method = "#{class_name}/#{{{method}}}" ({{paths}}).each do |path| - @@only_routes_tree.add class_name_method + path, '/' + method_downcase + path + @@only_routes_tree.add class_name_method + path, '/' + {{method}} + path end end macro exclude(paths, method = "GET") class_name = {{@type.name}} - method_downcase = {{method.downcase}} - class_name_method = "#{class_name}/#{method_downcase}" + class_name_method = "#{class_name}/#{{{method}}}" ({{paths}}).each do |path| - @@exclude_routes_tree.add class_name_method + path, '/' + method_downcase + path + @@exclude_routes_tree.add class_name_method + path, '/' + {{method}} + path end end @@ -74,7 +72,7 @@ module Kemal end private def radix_path(method : String, path : String) - "#{self.class}/#{method.downcase}#{path}" + "#{self.class}/#{method}#{path}" end end end diff --git a/src/kemal/route_handler.cr b/src/kemal/route_handler.cr index 216616a..698c6fd 100644 --- a/src/kemal/route_handler.cr +++ b/src/kemal/route_handler.cr @@ -57,7 +57,7 @@ module Kemal end private def radix_path(method, path) - '/' + method.downcase + path + '/' + method + path end private def add_to_radix_tree(method, path, route) From d20dbc783cd94d5d2096a619353db34f08e5bf99 Mon Sep 17 00:00:00 2001 From: Serdar Dogruyol <990485+sdogruyol@users.noreply.github.com> Date: Sun, 9 Oct 2022 13:52:08 +0300 Subject: [PATCH 03/33] Add CHANGELOG for 1.3.0 --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3bc639b..7b9bf2a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +# 1.3.0 (09-10-2022) +- Crystal 1.6.0 support :tada: +- Disable signal trap for usage Kemal with other tools [#642](https://github.com/kemalcr/kemal/pull/642). Thanks @le0pard :pray: +- Bump exception_page shard to v0.3.0 [#645](https://github.com/kemalcr/kemal/pull/645). Thanks @Sija :pray: +- ***(Security)*** Omitting filters fix for lowercase methods requests [#647](https://github.com/kemalcr/kemal/pull/647). Thanks @sdogruyol @SlayerShadow :pray: + # 1.2.0 (07-07-2022) - Crystal 1.5.0 support :tada: From ae7cda829162ea27668b3fc79cd1868026e25098 Mon Sep 17 00:00:00 2001 From: Serdar Dogruyol <990485+sdogruyol@users.noreply.github.com> Date: Sun, 9 Oct 2022 13:54:10 +0300 Subject: [PATCH 04/33] Bump version to 1.3.0 --- shard.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shard.yml b/shard.yml index 2f10371..3b9014d 100644 --- a/shard.yml +++ b/shard.yml @@ -1,5 +1,5 @@ name: kemal -version: 1.2.0 +version: 1.3.0 authors: - Serdar Dogruyol From 19661893ca98c69ef852579e4076369279b68857 Mon Sep 17 00:00:00 2001 From: Aravinda Vishwanathapura Date: Fri, 17 Feb 2023 10:43:54 +0530 Subject: [PATCH 05/33] Fix multiple logger handlers when custom logger is used (#653) --- src/kemal/helpers/helpers.cr | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/kemal/helpers/helpers.cr b/src/kemal/helpers/helpers.cr index 11a6b62..a829808 100644 --- a/src/kemal/helpers/helpers.cr +++ b/src/kemal/helpers/helpers.cr @@ -48,13 +48,13 @@ end # This is used to replace the built-in `Kemal::LogHandler` with a custom logger. # # A custom logger must inherit from `Kemal::BaseLogHandler` and must implement -# `call(env)`, `write(message)` methods. +# `call(context)`, `write(message)` methods. # # ``` # class MyCustomLogger < Kemal::BaseLogHandler -# def call(env) +# def call(context) # puts "I'm logging some custom stuff here." -# call_next(env) # => This calls the next handler +# call_next(context) # => This calls the next handler # end # # # This is used from `log` method. @@ -71,7 +71,6 @@ end # ``` def logger(logger : Kemal::BaseLogHandler) Kemal.config.logger = logger - Kemal.config.add_handler logger end # Enables / Disables static file serving. From 6a10ea8127763d59468030b46a411d60617a71f7 Mon Sep 17 00:00:00 2001 From: Anton Maminov Date: Fri, 17 Feb 2023 07:18:37 +0200 Subject: [PATCH 06/33] bump ameba to 1.4.0 and fix warnings (#654) --- .ameba.yml | 19 +++++++++++++++---- shard.yml | 2 +- spec/config_spec.cr | 2 +- src/kemal.cr | 2 +- src/kemal/config.cr | 6 +++--- src/kemal/init_handler.cr | 2 +- 6 files changed, 22 insertions(+), 11 deletions(-) diff --git a/.ameba.yml b/.ameba.yml index 9cc083d..d7b2b9a 100644 --- a/.ameba.yml +++ b/.ameba.yml @@ -1,13 +1,24 @@ # This configuration file was generated by `ameba --gen-config` -# on 2019-08-25 09:29:24 UTC using Ameba version 0.10.0. +# on 2023-01-30 12:35:15 UTC using Ameba version 1.4.0. # The point is for the user to remove these configuration records # one by one as the reported problems are removed from the code base. -# Problems found: 7 +# Problems found: 2 # Run `ameba --only Lint/UselessAssign` for details Lint/UselessAssign: Description: Disallows useless variable assignments - Enabled: true - Severity: Warning Excluded: - spec/view_spec.cr + Enabled: true + Severity: Warning + +# Problems found: 6 +# Run `ameba --only Lint/NotNil` for details +Lint/NotNil: + Description: Identifies usage of `not_nil!` calls + Excluded: + - src/kemal/param_parser.cr + - src/kemal/static_file_handler.cr + - src/kemal/config.cr + Enabled: true + Severity: Warning diff --git a/shard.yml b/shard.yml index 3b9014d..f8b9a77 100644 --- a/shard.yml +++ b/shard.yml @@ -15,7 +15,7 @@ dependencies: development_dependencies: ameba: github: crystal-ameba/ameba - version: ~> 1.0 + version: ~> 1.4.0 crystal: ">= 0.36.0" diff --git a/spec/config_spec.cr b/spec/config_spec.cr index daa6849..a7fc35c 100644 --- a/spec/config_spec.cr +++ b/spec/config_spec.cr @@ -16,7 +16,7 @@ describe "Config" do end it "sets default powered_by_header to true" do - Kemal::Config.new.powered_by_header.should be_true + Kemal::Config.new.powered_by_header?.should be_true end it "sets host binding" do diff --git a/src/kemal.cr b/src/kemal.cr index 9222fe6..5d43f15 100644 --- a/src/kemal.cr +++ b/src/kemal.cr @@ -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, trap_signal : Bool = true, &) Kemal::CLI.new args config = Kemal.config config.setup diff --git a/src/kemal/config.cr b/src/kemal/config.cr index 23372dd..3716960 100644 --- a/src/kemal/config.cr +++ b/src/kemal/config.cr @@ -20,11 +20,11 @@ module Kemal @ssl : OpenSSL::SSL::Context::Server? {% end %} - property host_binding, ssl, port, env, public_folder, logging, running + property app_name, host_binding, ssl, port, env, public_folder, logging, running property always_rescue, server : HTTP::Server?, extra_options, shutdown_message property serve_static : (Bool | Hash(String, Bool)) property static_headers : (HTTP::Server::Response, String, File::Info -> Void)? - property powered_by_header : Bool = true, app_name + property? powered_by_header : Bool = true def initialize @app_name = "Kemal" @@ -159,7 +159,7 @@ module Kemal end end - def self.config + def self.config(&) yield Config::INSTANCE end diff --git a/src/kemal/init_handler.cr b/src/kemal/init_handler.cr index 881325b..318576a 100644 --- a/src/kemal/init_handler.cr +++ b/src/kemal/init_handler.cr @@ -7,7 +7,7 @@ module Kemal INSTANCE = new def call(context : HTTP::Server::Context) - context.response.headers.add "X-Powered-By", "Kemal" if Kemal.config.powered_by_header + context.response.headers.add "X-Powered-By", "Kemal" if Kemal.config.powered_by_header? context.response.content_type = "text/html" unless context.response.headers.has_key?("Content-Type") call_next context end From 84ea6627acdc68015d767dfbd3bcdaa1a31c7707 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Serdar=20Dogruyol=20-=20Sedo=20=E3=82=BB=E3=83=89?= <990485+sdogruyol@users.noreply.github.com> Date: Sun, 19 Feb 2023 08:59:30 +0300 Subject: [PATCH 07/33] Add Kemal::OverrideMethodHandler (#651) --- spec/override_method_handler_spec.cr | 29 ++++++++++++++++++++++++ src/kemal/override_method_handler.cr | 33 ++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 spec/override_method_handler_spec.cr create mode 100644 src/kemal/override_method_handler.cr diff --git a/spec/override_method_handler_spec.cr b/spec/override_method_handler_spec.cr new file mode 100644 index 0000000..2b34e29 --- /dev/null +++ b/spec/override_method_handler_spec.cr @@ -0,0 +1,29 @@ +require "./spec_helper" + +describe "Kemal::OverrideMethodHandler" do + it "does not override method without _method for POST requests" do + request = HTTP::Request.new( + "POST", + "/", + body: "_not_method=PATCH", + headers: HTTP::Headers{"Content-Type" => "application/x-www-form-urlencoded; charset=UTF-8"} + ) + + context = create_request_and_return_io_and_context(Kemal::OverrideMethodHandler::INSTANCE, request)[1] + + context.request.method.should eq "POST" + end + + it "overrides method with _method for POST requests" do + request = HTTP::Request.new( + "POST", + "/", + body: "_method=PATCH", + headers: HTTP::Headers{"Content-Type" => "application/x-www-form-urlencoded; charset=UTF-8"} + ) + + context = create_request_and_return_io_and_context(Kemal::OverrideMethodHandler::INSTANCE, request)[1] + + context.request.method.should eq "PATCH" + end +end diff --git a/src/kemal/override_method_handler.cr b/src/kemal/override_method_handler.cr new file mode 100644 index 0000000..523e9e6 --- /dev/null +++ b/src/kemal/override_method_handler.cr @@ -0,0 +1,33 @@ +module Kemal + # Adds support for `_method` magic parameter to simulate PUT, PATCH, DELETE requests in an html form. + # + # This middleware is **not** in the default Kemal handlers. You need to explicitly add this to your handlers: + # + # ```ruby + # add_handler Kemal::OverrideMethodHandler + # ``` + # + # **Important:** This middleware consumes `params.body` to read the `_method` magic parameter. + class OverrideMethodHandler + include HTTP::Handler + INSTANCE = new + + ALLOWED_METHODS = ["PUT", "PATCH", "DELETE"] + OVERRIDE_METHOD = "POST" + OVERRIDE_METHOD_PARAM_KEY = "_method" + + def call(context) + request = context.request + if request.method == OVERRIDE_METHOD + if context.params.body.has_key?(OVERRIDE_METHOD_PARAM_KEY) && override_method_valid?(context.params.body[OVERRIDE_METHOD_PARAM_KEY]) + request.method = context.params.body["_method"].upcase + end + end + call_next(context) + end + + private def override_method_valid?(override_method : String) + ALLOWED_METHODS.includes?(override_method.upcase) + end + end +end From 8ebe171279a8fff80dd4b207541062731a5eda13 Mon Sep 17 00:00:00 2001 From: Mike Robbins Date: Tue, 21 Feb 2023 23:34:47 -0500 Subject: [PATCH 08/33] HeadRequestHandler: run GET handler and don't return the body (#655) --- spec/config_spec.cr | 2 +- spec/head_request_handler_spec.cr | 37 +++++++++++++++++++ spec/helpers_spec.cr | 4 +-- src/kemal/config.cr | 6 ++++ src/kemal/head_request_handler.cr | 60 +++++++++++++++++++++++++++++++ src/kemal/helpers/helpers.cr | 5 +-- src/kemal/route_handler.cr | 9 +++-- 7 files changed, 115 insertions(+), 8 deletions(-) create mode 100644 spec/head_request_handler_spec.cr create mode 100644 src/kemal/head_request_handler.cr diff --git a/spec/config_spec.cr b/spec/config_spec.cr index a7fc35c..11aa6d3 100644 --- a/spec/config_spec.cr +++ b/spec/config_spec.cr @@ -29,7 +29,7 @@ describe "Config" do config = Kemal.config config.add_handler CustomTestHandler.new Kemal.config.setup - config.handlers.size.should eq(7) + config.handlers.size.should eq(8) end it "toggles the shutdown message" do diff --git a/spec/head_request_handler_spec.cr b/spec/head_request_handler_spec.cr new file mode 100644 index 0000000..dc53970 --- /dev/null +++ b/spec/head_request_handler_spec.cr @@ -0,0 +1,37 @@ +require "./spec_helper" + +describe "Kemal::HeadRequestHandler" do + it "implicitly handles GET endpoints, with Content-Length header" do + get "/" do + "hello" + end + request = HTTP::Request.new("HEAD", "/") + client_response = call_request_on_app(request) + client_response.body.should eq("") + client_response.headers["Content-Length"].should eq("5") + end + + it "prefers explicit HEAD endpoint if specified" do + Kemal::RouteHandler::INSTANCE.add_route("HEAD", "/") { "hello" } + get "/" do + raise "shouldn't be called!" + end + request = HTTP::Request.new("HEAD", "/") + client_response = call_request_on_app(request) + client_response.body.should eq("") + client_response.headers["Content-Length"].should eq("5") + end + + it "gives compressed Content-Length when gzip enabled" do + gzip true + get "/" do + "hello" + end + headers = HTTP::Headers{"Accept-Encoding" => "gzip"} + request = HTTP::Request.new("HEAD", "/", headers) + client_response = call_request_on_app(request) + client_response.body.should eq("") + client_response.headers["Content-Encoding"].should eq("gzip") + client_response.headers["Content-Length"].should eq("25") + end +end diff --git a/spec/helpers_spec.cr b/spec/helpers_spec.cr index d22687b..ec587bb 100644 --- a/spec/helpers_spec.cr +++ b/spec/helpers_spec.cr @@ -13,7 +13,7 @@ describe "Macros" do it "adds a custom handler" do add_handler CustomTestHandler.new Kemal.config.setup - Kemal.config.handlers.size.should eq 7 + Kemal.config.handlers.size.should eq 8 end end @@ -150,7 +150,7 @@ describe "Macros" do it "adds HTTP::CompressHandler to handlers" do gzip true Kemal.config.setup - Kemal.config.handlers[4].should be_a(HTTP::CompressHandler) + Kemal.config.handlers[5].should be_a(HTTP::CompressHandler) end end diff --git a/src/kemal/config.cr b/src/kemal/config.cr index 3716960..02065b4 100644 --- a/src/kemal/config.cr +++ b/src/kemal/config.cr @@ -103,6 +103,7 @@ module Kemal unless @default_handlers_setup && @router_included setup_init_handler setup_log_handler + setup_head_request_handler setup_error_handler setup_static_file_handler setup_custom_handlers @@ -129,6 +130,11 @@ module Kemal @handler_position += 1 end + private def setup_head_request_handler + HANDLERS.insert(@handler_position, Kemal::HeadRequestHandler::INSTANCE) + @handler_position += 1 + end + private def setup_error_handler if @always_rescue @error_handler ||= Kemal::ExceptionHandler.new diff --git a/src/kemal/head_request_handler.cr b/src/kemal/head_request_handler.cr new file mode 100644 index 0000000..8dfcf50 --- /dev/null +++ b/src/kemal/head_request_handler.cr @@ -0,0 +1,60 @@ +require "http/server/handler" + +module Kemal + class HeadRequestHandler + include HTTP::Handler + + INSTANCE = new + + private class NullIO < IO + @original_output : IO + @out_count : Int32 + @response : HTTP::Server::Response + + def initialize(@response) + @closed = false + @original_output = @response.output + @out_count = 0 + end + + def read(slice : Bytes) + raise NotImplementedError.new("read") + end + + def write(slice : Bytes) : Nil + @out_count += slice.bytesize + end + + def close : Nil + return if @closed + @closed = true + + # Matching HTTP::Server::Response#close behavior: + # Conditionally determine based on status if the `content-length` header should be added automatically. + # See https://tools.ietf.org/html/rfc7230#section-3.3.2. + status = @response.status + set_content_length = !(status.not_modified? || status.no_content? || status.informational?) + + if !@response.headers.has_key?("Content-Length") && set_content_length + @response.content_length = @out_count + end + + @original_output.close + end + + def closed? : Bool + @closed + end + end + + def call(context) : Nil + if context.request.method == "HEAD" + # Capture and count bytes of response body generated on HEAD requests without actually sending the body back. + capture_io = NullIO.new(context.response) + context.response.output = capture_io + end + + call_next(context) + end + end +end diff --git a/src/kemal/helpers/helpers.cr b/src/kemal/helpers/helpers.cr index a829808..0be1a94 100644 --- a/src/kemal/helpers/helpers.cr +++ b/src/kemal/helpers/helpers.cr @@ -5,11 +5,12 @@ require "mime" # Adds given `Kemal::Handler` to handlers chain. -# There are 5 handlers by default and all the custom handlers -# goes between the first 4 and the last `Kemal::RouteHandler`. +# There are 6 handlers by default and all the custom handlers +# goes between the first 5 and the last `Kemal::RouteHandler`. # # - `Kemal::InitHandler` # - `Kemal::LogHandler` +# - `Kemal::HeadRequestHandler` # - `Kemal::ExceptionHandler` # - `Kemal::StaticFileHandler` # - Here goes custom handlers diff --git a/src/kemal/route_handler.cr b/src/kemal/route_handler.cr index 698c6fd..44c8c92 100644 --- a/src/kemal/route_handler.cr +++ b/src/kemal/route_handler.cr @@ -17,11 +17,9 @@ module Kemal process_request(context) end - # Adds a given route to routing tree. As an exception each `GET` route additionaly defines - # a corresponding `HEAD` route. + # Adds a given route to routing tree. def add_route(method : String, path : String, &handler : HTTP::Server::Context -> _) add_to_radix_tree method, path, Route.new(method, path, &handler) - add_to_radix_tree("HEAD", path, Route.new("HEAD", path) { }) if method == "GET" end # Looks up the route from the Radix::Tree for the first time and caches to improve performance. @@ -34,6 +32,11 @@ module Kemal route = @routes.find(lookup_path) + if verb == "HEAD" && !route.found? + # On HEAD requests, implicitly fallback to running the GET handler. + route = @routes.find(radix_path("GET", path)) + end + if route.found? @cached_routes.clear if @cached_routes.size == CACHED_ROUTES_LIMIT @cached_routes[lookup_path] = route From aa004afbbf8a980c010b7e19294ea901ff29a00b Mon Sep 17 00:00:00 2001 From: Serdar Dogruyol <990485+sdogruyol@users.noreply.github.com> Date: Sat, 15 Apr 2023 11:30:57 +0300 Subject: [PATCH 09/33] Add CHANGELOG for 1.4.0 --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b9bf2a..8a0c0e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# 1.4.0 (15-04-2023) + +- Crystal 1.8.0 support :tada: +- Fix multiple logger handlers when custom logger is used [#653](https://github.com/kemalcr/kemal/pull/653). Thanks @aravindavk :pray: +- Add Kemal::OverrideMethodHandler [#651](https://github.com/kemalcr/kemal/pull/651). Thanks @sdogruyol :pray: +- HeadRequestHandler: run GET handler and don't return the body [#655](https://github.com/kemalcr/kemal/pull/655). Thanks @compumike :pray: + # 1.3.0 (09-10-2022) - Crystal 1.6.0 support :tada: - Disable signal trap for usage Kemal with other tools [#642](https://github.com/kemalcr/kemal/pull/642). Thanks @le0pard :pray: From c995a2a9711d72d90b347683007b45bbaf495317 Mon Sep 17 00:00:00 2001 From: Serdar Dogruyol <990485+sdogruyol@users.noreply.github.com> Date: Sat, 15 Apr 2023 11:31:16 +0300 Subject: [PATCH 10/33] Bump version to 1.4.0 --- shard.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shard.yml b/shard.yml index f8b9a77..09a5592 100644 --- a/shard.yml +++ b/shard.yml @@ -1,5 +1,5 @@ name: kemal -version: 1.3.0 +version: 1.4.0 authors: - Serdar Dogruyol From a939a577de0a184403d29cef59a26789fa901383 Mon Sep 17 00:00:00 2001 From: Giorgi Kavrelishvili Date: Sat, 23 Sep 2023 11:48:45 +0400 Subject: [PATCH 11/33] Fixes the issues with Ameba types --- shard.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/shard.yml b/shard.yml index 09a5592..2ca838e 100644 --- a/shard.yml +++ b/shard.yml @@ -15,7 +15,6 @@ dependencies: development_dependencies: ameba: github: crystal-ameba/ameba - version: ~> 1.4.0 crystal: ">= 0.36.0" From cb9adcd188162f1e3c83ff98263d3587b9a2f9ab Mon Sep 17 00:00:00 2001 From: Thomas Fini Hansen Date: Sun, 24 Sep 2023 08:35:08 +0200 Subject: [PATCH 12/33] Allow HTTP::Server::Context#redirect to take an URL (#659) --- src/kemal/ext/context.cr | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/kemal/ext/context.cr b/src/kemal/ext/context.cr index 70787b6..d677d08 100644 --- a/src/kemal/ext/context.cr +++ b/src/kemal/ext/context.cr @@ -17,8 +17,8 @@ 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) - @response.headers.add "Location", url + def redirect(url : String | URI, status_code : Int32 = 302, *, body : String? = nil, close : Bool = true) + @response.headers.add "Location", url.to_s @response.status_code = status_code @response.print(body) if body @response.close if close From 13fd4f8b2f5d3003d0ad0c43e0b3a0f324abefb0 Mon Sep 17 00:00:00 2001 From: Val Packett Date: Tue, 31 Oct 2023 07:34:15 -0300 Subject: [PATCH 13/33] helpers: Support building without_zlib (#667) --- src/kemal/helpers/helpers.cr | 57 ++++++++++++++++++++---------------- 1 file changed, 31 insertions(+), 26 deletions(-) diff --git a/src/kemal/helpers/helpers.cr b/src/kemal/helpers/helpers.cr index 0be1a94..1b733c3 100644 --- a/src/kemal/helpers/helpers.cr +++ b/src/kemal/helpers/helpers.cr @@ -1,4 +1,4 @@ -{% if compare_versions(Crystal::VERSION, "0.35.0-0") >= 0 %} +{% if compare_versions(Crystal::VERSION, "0.35.0-0") >= 0 && !flag?(:without_zlib) %} require "compress/deflate" require "compress/gzip" {% end %} @@ -141,33 +141,38 @@ def send_file(env : HTTP::Server::Context, path : String, mime_type : String? = next multipart(file, env) end - condition = config.is_a?(Hash) && config["gzip"]? == true && filesize > minsize && Kemal::Utils.zip_types(file_path) - if condition && request_headers.includes_word?("Accept-Encoding", "gzip") - env.response.headers["Content-Encoding"] = "gzip" - {% if compare_versions(Crystal::VERSION, "0.35.0-0") >= 0 %} - Compress::Gzip::Writer.open(env.response) do |deflate| - IO.copy(file, deflate) - end - {% else %} - Gzip::Writer.open(env.response) do |deflate| - IO.copy(file, deflate) - end - {% end %} - elsif condition && request_headers.includes_word?("Accept-Encoding", "deflate") - env.response.headers["Content-Encoding"] = "deflate" - {% if compare_versions(Crystal::VERSION, "0.35.0-0") >= 0 %} - Compress::Deflate::Writer.open(env.response) do |deflate| - IO.copy(file, deflate) - end - {% else %} - Flate::Writer.open(env.response) do |deflate| - IO.copy(file, deflate) - end - {% end %} - else + {% if flag?(:without_zlib) %} env.response.content_length = filesize IO.copy(file, env.response) - end + {% else %} + condition = config.is_a?(Hash) && config["gzip"]? == true && filesize > minsize && Kemal::Utils.zip_types(file_path) + if condition && request_headers.includes_word?("Accept-Encoding", "gzip") + env.response.headers["Content-Encoding"] = "gzip" + {% if compare_versions(Crystal::VERSION, "0.35.0-0") >= 0 %} + Compress::Gzip::Writer.open(env.response) do |deflate| + IO.copy(file, deflate) + end + {% else %} + Gzip::Writer.open(env.response) do |deflate| + IO.copy(file, deflate) + end + {% end %} + elsif condition && request_headers.includes_word?("Accept-Encoding", "deflate") + env.response.headers["Content-Encoding"] = "deflate" + {% if compare_versions(Crystal::VERSION, "0.35.0-0") >= 0 %} + Compress::Deflate::Writer.open(env.response) do |deflate| + IO.copy(file, deflate) + end + {% else %} + Flate::Writer.open(env.response) do |deflate| + IO.copy(file, deflate) + end + {% end %} + else + env.response.content_length = filesize + IO.copy(file, env.response) + end + {% end %} end return end From 0de0e990e9db7f1c930b7e43ac2aa5bd23aed07e Mon Sep 17 00:00:00 2001 From: Giorgi Kavrelishvili Date: Tue, 23 Jan 2024 20:07:39 +0400 Subject: [PATCH 14/33] Move to Process.on_interupt (#666) --- src/kemal.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/kemal.cr b/src/kemal.cr index 5d43f15..843f89e 100644 --- a/src/kemal.cr +++ b/src/kemal.cr @@ -89,7 +89,7 @@ module Kemal end private def self.setup_trap_signal - Signal::INT.trap do + Process.on_interrupt do log "#{Kemal.config.app_name} is going to take a rest!" if Kemal.config.shutdown_message Kemal.stop exit From 9628043e4cf6c0da8720b12d7613afd473f753b1 Mon Sep 17 00:00:00 2001 From: Sijawusz Pur Rahnama Date: Tue, 23 Jan 2024 17:08:49 +0100 Subject: [PATCH 15/33] Bump `exception_page` dependency (#669) --- shard.yml | 2 +- spec/websocket_handler_spec.cr | 2 +- src/kemal/config.cr | 4 ++-- src/kemal/ext/context.cr | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/shard.yml b/shard.yml index 2ca838e..594ff04 100644 --- a/shard.yml +++ b/shard.yml @@ -10,7 +10,7 @@ dependencies: version: ~> 0.4.0 exception_page: github: crystal-loot/exception_page - version: ~> 0.3.0 + version: ~> 0.4.1 development_dependencies: ameba: diff --git a/spec/websocket_handler_spec.cr b/spec/websocket_handler_spec.cr index 64f5489..123a9f9 100644 --- a/spec/websocket_handler_spec.cr +++ b/spec/websocket_handler_spec.cr @@ -38,7 +38,7 @@ describe "Kemal::WebSocketHandler" do it "fetches named url parameters" do handler = Kemal::WebSocketHandler::INSTANCE - ws "/:id" { |_, c| c.ws_route_lookup.params["id"] } + ws "/:id" { |_, context| context.ws_route_lookup.params["id"] } headers = HTTP::Headers{ "Upgrade" => "websocket", "Connection" => "Upgrade", diff --git a/src/kemal/config.cr b/src/kemal/config.cr index 02065b4..df7330a 100644 --- a/src/kemal/config.cr +++ b/src/kemal/config.cr @@ -159,8 +159,8 @@ module Kemal end private def setup_filter_handlers - FILTER_HANDLERS.each do |h| - HANDLERS.insert(@handler_position, h) + FILTER_HANDLERS.each do |handler| + HANDLERS.insert(@handler_position, handler) end end end diff --git a/src/kemal/ext/context.cr b/src/kemal/ext/context.cr index d677d08..545111f 100644 --- a/src/kemal/ext/context.cr +++ b/src/kemal/ext/context.cr @@ -9,7 +9,7 @@ class HTTP::Server STORE_MAPPINGS = [Nil, String, Int32, Int64, Float64, Bool] macro finished - alias StoreTypes = Union({{ *STORE_MAPPINGS }}) + alias StoreTypes = Union({{ STORE_MAPPINGS.splat }}) @store = {} of String => StoreTypes end From bb9105f202df3cfa87197f71dce0dd20194c1bfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Serdar=20Dogruyol=20-=20Sedo=20=E3=82=BB=E3=83=89?= <990485+sdogruyol@users.noreply.github.com> Date: Thu, 1 Feb 2024 12:50:40 +0300 Subject: [PATCH 16/33] Add message support to Kemal::Exceptions::CustomException (#671) --- src/kemal/helpers/exceptions.cr | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/kemal/helpers/exceptions.cr b/src/kemal/helpers/exceptions.cr index 53520b3..666cdd9 100644 --- a/src/kemal/helpers/exceptions.cr +++ b/src/kemal/helpers/exceptions.cr @@ -13,7 +13,10 @@ module Kemal::Exceptions end class CustomException < Exception - def initialize(context : HTTP::Server::Context) + def initialize(context : HTTP::Server::Context, message : String = "") + @context = context + @message = message + super "Rendered error with #{context.response.status_code}" end end From bef73510003637fa74aab93cf7d98cf287978128 Mon Sep 17 00:00:00 2001 From: Serdar Dogruyol <990485+sdogruyol@users.noreply.github.com> Date: Sat, 3 Feb 2024 13:48:18 +0300 Subject: [PATCH 17/33] Don't call super on CustomException --- src/kemal/helpers/exceptions.cr | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/kemal/helpers/exceptions.cr b/src/kemal/helpers/exceptions.cr index 666cdd9..a463cba 100644 --- a/src/kemal/helpers/exceptions.cr +++ b/src/kemal/helpers/exceptions.cr @@ -16,8 +16,6 @@ module Kemal::Exceptions def initialize(context : HTTP::Server::Context, message : String = "") @context = context @message = message - - super "Rendered error with #{context.response.status_code}" end end end From 7c47bbc15069a7ef70ae50db83a80714b6ec5ae7 Mon Sep 17 00:00:00 2001 From: Sijawusz Pur Rahnama Date: Wed, 14 Feb 2024 11:33:05 +0100 Subject: [PATCH 18/33] Fix recent changes to `CustomException` (#673) --- src/kemal/helpers/exceptions.cr | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/kemal/helpers/exceptions.cr b/src/kemal/helpers/exceptions.cr index a463cba..cda5e59 100644 --- a/src/kemal/helpers/exceptions.cr +++ b/src/kemal/helpers/exceptions.cr @@ -13,9 +13,9 @@ module Kemal::Exceptions end class CustomException < Exception - def initialize(context : HTTP::Server::Context, message : String = "") - @context = context - @message = message + def initialize(@context : HTTP::Server::Context, message : String? = nil) + message ||= "Rendered error with #{context.response.status_code}" + super message end end end From b074578c1a316c5185320e1029fc1971b3303645 Mon Sep 17 00:00:00 2001 From: Sijawusz Pur Rahnama Date: Wed, 10 Apr 2024 18:26:04 +0200 Subject: [PATCH 19/33] Add `Date` header to HTTP responses (#676) --- spec/init_handler_spec.cr | 12 ++++++++++++ src/kemal/init_handler.cr | 3 +++ 2 files changed, 15 insertions(+) diff --git a/spec/init_handler_spec.cr b/spec/init_handler_spec.cr index 601bbc1..20724c6 100644 --- a/spec/init_handler_spec.cr +++ b/spec/init_handler_spec.cr @@ -11,6 +11,18 @@ describe "Kemal::InitHandler" do context.response.headers["Content-Type"].should eq "text/html" end + it "initializes context with Date header" do + request = HTTP::Request.new("GET", "/") + io = IO::Memory.new + response = HTTP::Server::Response.new(io) + context = HTTP::Server::Context.new(request, response) + Kemal::InitHandler::INSTANCE.next = ->(_context : HTTP::Server::Context) {} + Kemal::InitHandler::INSTANCE.call(context) + date = context.response.headers["Date"]?.should_not be_nil + date = HTTP.parse_time(date).should_not be_nil + date.should be_close(Time.utc, 1.second) + end + it "initializes context with X-Powered-By: Kemal" do request = HTTP::Request.new("GET", "/") io = IO::Memory.new diff --git a/src/kemal/init_handler.cr b/src/kemal/init_handler.cr index 318576a..5ce04b1 100644 --- a/src/kemal/init_handler.cr +++ b/src/kemal/init_handler.cr @@ -1,3 +1,5 @@ +require "http" + module Kemal # Initializes the context with default values, such as # *Content-Type* or *X-Powered-By* headers. @@ -9,6 +11,7 @@ module Kemal def call(context : HTTP::Server::Context) context.response.headers.add "X-Powered-By", "Kemal" if Kemal.config.powered_by_header? context.response.content_type = "text/html" unless context.response.headers.has_key?("Content-Type") + context.response.headers.add "Date", HTTP.format_time(Time.utc) call_next context end end From a15ba83175a7e44642f0a57d4bbe1c3ebf75b160 Mon Sep 17 00:00:00 2001 From: Sijawusz Pur Rahnama Date: Wed, 10 Apr 2024 18:31:48 +0200 Subject: [PATCH 20/33] Tweak CHANGELOG.md (#677) --- CHANGELOG.md | 357 ++++++++++++++++++++++++--------------------------- 1 file changed, 170 insertions(+), 187 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a0c0e0..71dba1c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,10 +2,11 @@ - Crystal 1.8.0 support :tada: - Fix multiple logger handlers when custom logger is used [#653](https://github.com/kemalcr/kemal/pull/653). Thanks @aravindavk :pray: -- Add Kemal::OverrideMethodHandler [#651](https://github.com/kemalcr/kemal/pull/651). Thanks @sdogruyol :pray: -- HeadRequestHandler: run GET handler and don't return the body [#655](https://github.com/kemalcr/kemal/pull/655). Thanks @compumike :pray: +- Add `Kemal::OverrideMethodHandler` [#651](https://github.com/kemalcr/kemal/pull/651). Thanks @sdogruyol :pray: +- `HeadRequestHandler`: run GET handler and don't return the body [#655](https://github.com/kemalcr/kemal/pull/655). Thanks @compumike :pray: # 1.3.0 (09-10-2022) + - Crystal 1.6.0 support :tada: - Disable signal trap for usage Kemal with other tools [#642](https://github.com/kemalcr/kemal/pull/642). Thanks @le0pard :pray: - Bump exception_page shard to v0.3.0 [#645](https://github.com/kemalcr/kemal/pull/645). Thanks @Sija :pray: @@ -15,15 +16,15 @@ - 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: +- 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 + You can enable it via: -```crystal + ```crystal serve_static({"dir_index" => true}) -``` + ``` # 1.1.2 (24-02-2022) @@ -31,7 +32,9 @@ You can enable it via # 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: +- Remove Kilt [#618](https://github.com/kemalcr/kemal/pull/618). Thanks @sdogruyol :pray: +- 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: @@ -44,7 +47,7 @@ You can enable it via - Crystal 1.0.0 support :tada: - Update Radix to use latest 0.4.0 [#596](https://github.com/kemalcr/kemal/pull/596). Thanks @luislavena :pray: - Use latest version of Ameba dependency (dev) [#597](https://github.com/kemalcr/kemal/pull/597). Thanks @luislavena :pray: -- Fix StaticFileHandler failing spec [#599](https://github.com/kemalcr/kemal/pull/599). Thanks @jinn999 :pray: +- Fix `StaticFileHandler` failing spec [#599](https://github.com/kemalcr/kemal/pull/599). Thanks @jinn999 :pray: # 0.27.0 (28-11-2020) @@ -70,88 +73,83 @@ You can enable it via # 0.25.2 (08-02-2019) - Add option to config to parse or not command line parameters [#483](https://github.com/kemalcr/kemal/pull/483). Thanks @diegogub :pray: - - Allow to set filename for `send_file` [#512](https://github.com/kemalcr/kemal/pull/512). Thanks @mamantoha :pray: - -```ruby -send_file env, "./asset/image.jpeg", filename: "image.jpg" -``` + ```crystal + send_file env, "./asset/image.jpeg", filename: "image.jpg" + ``` - Set `status_code` before response [#513](https://github.com/kemalcr/kemal/pull/513). Thanks @mamantohoa :pray: - - Use Crystal MIME registry. [#516](https://github.com/kemalcr/kemal/pull/516) Thanks @Sija :pray: # 0.25.1 (06-10-2018) -- Fix `params.files` memoization https://github.com/kemalcr/kemal/pull/503. Thanks @mamantoha :pray: +- Fix `params.files` memoization [#503](https://github.com/kemalcr/kemal/pull/503). Thanks @mamantoha :pray: # 0.25.0 (05-10-2018) - Crystal 0.27.0 support. - *[breaking change]* Added back `env.params.files`. -Here's a fully working sample for reading a image file upload `image1` and saving it under `public/uploads`. + Here's a fully working sample for reading a image file upload `image1` and saving it under `public/uploads`. -```crystal -post "/upload" do |env| - file = env.params.files["image1"].tempfile - file_path = ::File.join [Kemal.config.public_folder, "uploads/", File.basename(file.path)] - File.open(file_path, "w") do |f| - IO.copy(file, f) + ```crystal + post "/upload" do |env| + file = env.params.files["image1"].tempfile + file_path = ::File.join [Kemal.config.public_folder, "uploads/", File.basename(file.path)] + File.open(file_path, "w") do |f| + IO.copy(file, f) + end + "Upload ok" end - "Upload ok" -end -``` + ``` -To test + To test -`curl -F "image1=@/Users/serdar/Downloads/kemal.png" http://localhost:3000/upload` + `curl -F "image1=@/Users/serdar/Downloads/kemal.png" http://localhost:3000/upload` -- Cache HTTP routes to increase performance :rocket: https://github.com/kemalcr/kemal/pull/493 +- Cache HTTP routes to increase performance :rocket: [#493](https://github.com/kemalcr/kemal/pull/493) # 0.24.0 (14-08-2018) -- *[breaking change]* Removed `env.params.files`. You can use Crystal's built-in `HTTP::FormData.parse` instead +- *[breaking change]* Removed `env.params.files`. You can use Crystal's built-in `HTTP::FormData.parse` instead: -```ruby -post "/upload" do |env| - HTTP::FormData.parse(env.request) do |upload| - filename = file.filename + ```crystal + post "/upload" do |env| + HTTP::FormData.parse(env.request) do |upload| + filename = file.filename - if !filename.is_a?(String) - "No filename included in upload" - else - file_path = ::File.join [Kemal.config.public_folder, "uploads/", filename] - File.open(file_path, "w") do |f| - IO.copy(file.tmpfile, f) + if !filename.is_a?(String) + "No filename included in upload" + else + file_path = ::File.join [Kemal.config.public_folder, "uploads/", filename] + File.open(file_path, "w") do |f| + IO.copy(file.tmpfile, f) + end + "Upload OK" end - "Upload OK" end -end -``` + ``` - *[breaking change]* From now on to access dynamic url params in a WebSocket route you have to use: -```ruby -ws "/:id" do |socket, context| - id = context.ws_route_lookup.params["id"] -end -``` + ```crystal + ws "/:id" do |socket, context| + id = context.ws_route_lookup.params["id"] + end + ``` - *[breaking change]* Removed `_method` magic param. - - Added new exception page [#466](https://github.com/kemalcr/kemal/pull/466). Thanks @mamantoha 🙏 - - Support custom port binding. Thanks @straight-shoota 🙏 -```ruby -Kemal.run do |config| - server = config.server.not_nil! - server.bind_tcp "127.0.0.1", 3000, reuse_port: true - server.bind_tcp "0.0.0.0", 3001, reuse_port: true -end -``` + ```crystal + Kemal.run do |config| + server = config.server.not_nil! + server.bind_tcp "127.0.0.1", 3000, reuse_port: true + server.bind_tcp "0.0.0.0", 3001, reuse_port: true + end + ``` # 0.23.0 (17-06-2018) @@ -169,71 +167,69 @@ end - Allow videos to be opened with correct mime type. [#406](https://github.com/kemalcr/kemal/pull/406) thanks @crisward 🙏 - Add webm mime type.[#413](https://github.com/kemalcr/kemal/pull/413) thanks @reindeer-cafe 🙏 - # 0.21.0 (05-09-2017) - Dynamically insert handlers :muscle: Fixes [#376](https://github.com/kemalcr/kemal/pull/376). - Add context to WebSocket. This allows one to use `HTTP::Server::Context` in `ws` declarations :heart_eyes: Fixes [#349](https://github.com/kemalcr/kemal/pull/349). -```ruby -ws "/:room_name" do |socket, env| - env.params.url["room_name"] -end -``` + ```crystal + ws "/:room_name" do |socket, env| + env.params.url["room_name"] + end + ``` - Add support for customizing the headers of built-in `Kemal::StaticFileHandler` :hammer: Useful for supporting `CORS` for single page applications :clap: -```ruby -static_headers do |response, filepath, filestat| - if filepath =~ /\.html$/ + ```crystal + static_headers do |response, filepath, filestat| + if filepath =~ /\.html$/ response.headers.add("Access-Control-Allow-Origin", "*") end response.headers.add("Content-Size", filestat.size.to_s) end -end -``` + ``` -- Allow %w in Handler macros [#385](https://github.com/kemalcr/kemal/pull/385). Thanks @will :pray: - -- Security: X-Content-Type-Options: nosniff for static files. Fixes [#379](https://github.com/kemalcr/kemal/issues/379). Thanks @crisward :pray: - -- Performance: [Remove tempfile management to OS](https://github.com/kemalcr/kemal/commit/a1520de7ed3865fa73258343a80fad4f20666a99). This brings %10 - 15 performance boost to Kemal :rocket: +- Allow `%w` in Handler macros [#385](https://github.com/kemalcr/kemal/pull/385). Thanks @will :pray: +- Security: `X-Content-Type-Options: nosniff` for static files. Fixes [#379](https://github.com/kemalcr/kemal/issues/379). Thanks @crisward :pray: +- Performance: [Remove tempfile management to OS](https://github.com/kemalcr/kemal/commit/a1520de7ed3865fa73258343a80fad4f20666a99). This brings 10-15% performance boost to Kemal :rocket: # 0.20.0 (01-07-2017) - Crystal 0.23.0 support! As always, Kemal is compatible with the latest major release of Crystal 💎 - Great news everyone 🎉 All handlers are now completely ***customizable***!. Use the default `Kemal` handlers or go wild, it's all up to you ⛏ -```ruby -# Don't forget to add `Kemal::RouteHandler::INSTANCE` or your routes won't work! -Kemal.config.handlers = [Kemal::InitHandler.new, YourHandler.new, Kemal::RouteHandler::INSTANCE] -``` + ```crystal + # Don't forget to add `Kemal::RouteHandler::INSTANCE` or your routes won't work! + Kemal.config.handlers = [Kemal::InitHandler.new, YourHandler.new, Kemal::RouteHandler::INSTANCE] + ``` -You can also insert a handler into a specific position. + You can also insert a handler into a specific position. + + ```crystal + # This adds MyCustomHandler instance to 1 position. + # Be aware that the index starts from 0. + add_handler MyCustomHandler.new, 1 + ``` -```ruby -# This adds MyCustomHandler instance to 1 position. Be aware that the index starts from 0. -add_handler MyCustomHandler.new, 1 -``` - Updated [Kilt](https://github.com/jeromegn/kilt) to v0.4.0. - Make `Route` a `Struct`. This improves the performance of route declarations. # 0.19.0 (09-05-2017) -- Return no body for head route fixes #323. (thanks @crisward) -- Update `radix` to `0.3.8`. (thanks @waghanza) +- Return no body for head route fixes [#323](https://github.com/kemalcr/kemal/issues/323). (thanks @crisward) +- Update Radix to `v0.3.8`. (thanks @waghanza) - User defined context store types. (thanks @neovitange) -```ruby -class User - property name -end + ```crystal + class User + property name + end -add_context_storage_type(User) -``` + add_context_storage_type(User) + ``` -- Prevent `send_file returning filesize. (thanks @crisward) -- Dont call setup in `config#add_filter_handler` fixes #338. +- Prevent `send_file` returning filesize. (thanks @crisward) +- Don't call setup in `config#add_filter_handler` fixes [#338](https://github.com/kemalcr/kemal/issues/338). # 0.18.3 (07-03-2017) @@ -241,16 +237,14 @@ add_context_storage_type(User) # 0.18.2 (24-02-2017) -- Fix [Gzip in Kemal Seems broken for static files](https://github.com/kemalcr/kemal/issues/316). This was caused by `Gzip::Writer` in `Crystal 0.21.0` and currently mitigated by monkey patching `Gzip::Header`. +- Fix Gzip in Kemal Seems broken for static files [#316](https://github.com/kemalcr/kemal/issues/316). This was caused by `Gzip::Writer` in `Crystal 0.21.0` and currently mitigated by monkey patching `Gzip::Header`. # 0.18.1 (21-02-2017) - Crystal 0.21.0 support - Drop `multipart.cr` dependency. `multipart` support is now built-into Crystal <3 - Since Crystal 0.21.0 comes built-in with `multipart` there are some improvements and deprecations. - -`meta` has been removed from `FileUpload` and it has the following properties - +- `meta` has been removed from `FileUpload` and it has the following properties: + `tmpfile`: This is temporary file for file upload. Useful for saving the upload file. + `filename`: File name of the file upload. (logo.png, images.zip e.g) + `headers`: Headers for the file upload. @@ -259,40 +253,35 @@ add_context_storage_type(User) + `read_time`: Read time of the file upload. + `size`: Size of the file upload. - # 0.18.0 (11-02-2017) -- Simpler file upload. File uploads can now be access from `HTTP::Server::Context` like `env.params.files["filename"]`. +- Simpler file upload. File uploads can now be access from `HTTP::Server::Context` like `env.params.files["filename"]`, which exposes following properties. + + `tmpfile`: This is temporary file for file upload. Useful for saving the upload file. + + `tmpfile_path`: File path of `tmpfile`. + + `filename`: File name of the file upload. (logo.png, images.zip e.g) + + `meta`: Meta information for the file upload. + + `headers`: Headers for the file upload. -`env.params.files["filename"]` has 5 methods - -- `tmpfile`: This is temporary file for file upload. Useful for saving the upload file. -- `tmpfile_path`: File path of `tmpfile`. -- `filename`: File name of the file upload. (logo.png, images.zip e.g) -- `meta`: Meta information for the file upload. -- `headers`: Headers for the file upload. - -Here's a fully working sample for reading a image file upload `image1` and saving it under `public/uploads`. + Here's a fully working sample for reading a image file upload `image1` and saving it under `public/uploads`. ```crystal -post "/upload" do |env| - file = env.params.files["image1"].tmpfile - file_path = ::File.join [Kemal.config.public_folder, "uploads/", file.filename] - File.open(file_path, "w") do |f| - IO.copy(file, f) + post "/upload" do |env| + file = env.params.files["image1"].tmpfile + file_path = ::File.join [Kemal.config.public_folder, "uploads/", file.filename] + File.open(file_path, "w") do |f| + IO.copy(file, f) + end + "Upload ok" end - "Upload ok" -end ``` -To test + To test -`curl -F "image1=@/Users/serdar/Downloads/kemal.png" http://localhost:3000/upload` + `curl -F "image1=@/Users/serdar/Downloads/kemal.png" http://localhost:3000/upload` -- RF7233 support a.k.a file streaming. (https://github.com/kemalcr/kemal/pull/299) (thanks @denysvitali) - -- Update Radix to 0.3.7. Fixes https://github.com/kemalcr/kemal/issues/293 -- Configurable startup / shutdown logging. https://github.com/kemalcr/kemal/issues/291 and https://github.com/kemalcr/kemal/issues/292 (thanks @twisterghost). +- RF7233 support a.k.a file streaming. [#299](https://github.com/kemalcr/kemal/pull/299) (thanks @denysvitali) +- Update Radix to 0.3.7. Fixes [#293](https://github.com/kemalcr/kemal/issues/293) +- Configurable startup / shutdown logging. [#291](https://github.com/kemalcr/kemal/issues/291) and [#292](https://github.com/kemalcr/kemal/issues/292) (thanks @twisterghost). # 0.17.5 (09-01-2017) @@ -301,133 +290,127 @@ To test # 0.17.4 (24-12-2016) - Support for Crystal 0.20.3 -- Add `Kemal.stop`. Fixes #269. +- Add `Kemal.stop`. Fixes [#269](https://github.com/kemalcr/kemal/issues/269). - `HTTP::Handler` is not a class anymore, it's a module. See https://github.com/crystal-lang/crystal/releases/tag/0.20.3 # 0.17.3 (03-12-2016) -- Handle missing 404 image. Fixes #263 +- Handle missing 404 image. Fixes [#263](https://github.com/kemalcr/kemal/issues/263) - Remove basic auth middleware from core and move to [kemalcr/kemal-basic-auth](https://github.com/kemalcr/kemal-basic-auth). # 0.17.2 (25-11-2016) -- Use body.gets_to_end for parse_json. Fixes #260. +- Use `body.gets_to_end` for `parse_json`. Fixes #260. - Update Radix to 0.3.5 and lock pessimistically. (thanks @luislavena) # 0.17.1 (24-11-2016) -- Treat `HTTP::Request` body as an `IO`. Fixes [#257](https://github.com/sdogruyol/kemal/issues/257) +- Treat `HTTP::Request` body as an `IO`. Fixes [#257](https://github.com/kemalcr/kemal/issues/257) # 0.17.0 (23-11-2016) - Reimplemented Request middleware / filter routing. -Now all requests will first go through the Middleware stack then Filters (before_*) and will finally reach the matching route. + Now all requests will first go through the Middleware stack then Filters (`before_*`) and will finally reach the matching route. -Which is illustrated as, - -``` -Request -> Middleware -> Filter -> Route -``` + Which is illustrated as: `Request -> Middleware -> Filter -> Route` - Rename `return_with` as `halt`. -- Route declaration must start with `/`. Fixes [#242](https://github.com/sdogruyol/kemal/issues/242) -- Set default exception Content-Type to text/html. Fixes [#202](https://github.com/sdogruyol/kemal/issues/242) +- Route declaration must start with `/`. Fixes [#242](https://github.com/kemalcr/kemal/issues/242) +- Set default exception `Content-Type` to `text/html`. Fixes [#202](https://github.com/kemalcr/kemal/issues/242) - Add `only` and `exclude` paths for `Kemal::Handler`. This change requires that all handlers must inherit from `Kemal::Handler`. -For example this handler will only work on `/` path. By default the HTTP method is `GET`. + For example this handler will only work on `/` path. By default the HTTP method is `GET`. + ```crystal + class OnlyHandler < Kemal::Handler + only ["/"] -```crystal -class OnlyHandler < Kemal::Handler - only ["/"] - - def call(env) - return call_next(env) unless only_match?(env) - puts "If the path is / i will be doing some processing here." + def call(env) + return call_next(env) unless only_match?(env) + puts "If the path is / i will be doing some processing here." + end end -end -``` + ``` -The handlers using `exclude` will work on the paths that isn't specified. For example this handler will work on any routes other than `/`. + The handlers using `exclude` will work on the paths that isn't specified. For example this handler will work on any routes other than `/`. -```crystal -class ExcludeHandler < Kemal::Handler - exclude ["/"] + ```crystal + class ExcludeHandler < Kemal::Handler + exclude ["/"] - def call(env) - return call_next(env) unless only_match?(env) - puts "If the path is NOT / i will be doing some processing here." + def call(env) + return call_next(env) unless only_match?(env) + puts "If the path is NOT / i will be doing some processing here." + end end -end -``` + ``` - Close response on `halt`. (thanks @samueleaton). -- Update `Radix` to `v0.3.4`. -- `error` handler now also yields error. For example you can get the error mesasage like +- Update Radix to `v0.3.4`. +- `error` handler now also yields error. For example you can get the error mesasage like: -```crystal + ```crystal error 500 do |env, err| err.message end -``` + ``` - Update `multipart.cr` to `v0.1.1` # 0.16.1 (12-10-2016) -- Improved Multipart support with more info on parsed files. `parse_multipart(env)` now yields -an `UploadFile` object which has the following properties `field`,`data`,`meta`,`headers. +- Improved Multipart support with more info on parsed files. `parse_multipart(env)` now yields an `UploadFile` object which has the following properties: `field`, `data`, `meta` and `headers`. -```crystal -post "/upload" do |env| - parse_multipart(env) do |f| - image1 = f.data if f.field == "image1" - image2 = f.data if f.field == "image2" - puts f.meta - puts f.headers - "Upload complete" + ```crystal + post "/upload" do |env| + parse_multipart(env) do |f| + image1 = f.data if f.field == "image1" + image2 = f.data if f.field == "image2" + puts f.meta + puts f.headers + "Upload complete" + end end -end -``` + ``` # 0.16.0 - Multipart support <3 (thanks @RX14). Now you can handle file uploads. -```crystal -post "/upload" do |env| - parse_multipart(env) do |field, data| - image1 = data if field == "image1" - image2 = data if field == "image2" - "Upload complete" + ```crystal + post "/upload" do |env| + parse_multipart(env) do |field, data| + image1 = data if field == "image1" + image2 = data if field == "image2" + "Upload complete" + end end -end -``` + ``` -- Make session configurable. Now you can specify session name and expire time wit +- Make session configurable. Now you can specify session name and expire time with: -```crystal -Kemal.config.session["name"] = "your_app" -Kemal.config.session["expire_time"] = 48.hours -``` + ```crystal + Kemal.config.session["name"] = "your_app" + Kemal.config.session["expire_time"] = 48.hours + ``` -- Session now supports more types. (String, Int32, Float64, Bool) +- Session now supports more types. (`String`, `Int32`, `Float64`, `Bool`) - Add `gzip` helper to enable / disable gzip compression on responses. - Static file caching with etag and gzip (thanks @crisward) - `Kemal.run` now accepts port to listen. # 0.15.1 (05-09-2016) -- Don't forget to call_next on NullLogHandler +- Don't forget to `call_next` on `NullLogHandler` # 0.15.0 (03-09-2016) -- Add context store +- Add context store. - `KEMAL_ENV` respects to `Kemal.config.env` and needs to be explicitly set. - `Kemal::InitHandler` is introduced. Adds initial configuration, headers like `X-Powered-By`. - Add `send_file` to helpers. - Add mime types. -- Fix parsing JSON params when "charset" is present in "Content-Type" header. -- Use http-only cookie for session -- Inject STDOUT by default in CommonLogHandler +- Fix parsing JSON params when "charset" is present in `Content-Type` header. +- Use http-only cookie for session. +- Inject `STDOUT` by default in `CommonLogHandler`. From ca7d6e1e641e16eb5efa9969f40c3aef9655e4fc Mon Sep 17 00:00:00 2001 From: Serdar Dogruyol <990485+sdogruyol@users.noreply.github.com> Date: Wed, 10 Apr 2024 19:41:00 +0300 Subject: [PATCH 21/33] Update CHANGELOG --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 71dba1c..8ef7657 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +# 1.5.0 (10-04-2024) + +- Crystal 1.12.0 support :tada: +- Allow HTTP::Server::Context#redirect to take an URL [#659](https://github.com/kemalcr/kemal/pull/659). Thanks @xendk :pray: +- Bump `exception_page` dependency [#669](https://github.com/kemalcr/kemal/pull/669). Thanks @Sija :pray: +- Add message support to `Kemal::Exceptions::CustomException` [#671](https://github.com/kemalcr/kemal/pull/671). Thanks @sdogruyol :pray: +- Add `Date` header to HTTP responses [#676](https://github.com/kemalcr/kemal/pull/676). Thanks @Sija :pray: + # 1.4.0 (15-04-2023) - Crystal 1.8.0 support :tada: From 52ab623c50a544e9c321baced83ed674acbb2860 Mon Sep 17 00:00:00 2001 From: Serdar Dogruyol <990485+sdogruyol@users.noreply.github.com> Date: Wed, 10 Apr 2024 19:44:40 +0300 Subject: [PATCH 22/33] Bump version to 1.5.0 --- shard.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shard.yml b/shard.yml index 594ff04..4223a97 100644 --- a/shard.yml +++ b/shard.yml @@ -1,5 +1,5 @@ name: kemal -version: 1.4.0 +version: 1.5.0 authors: - Serdar Dogruyol From e69bd400b706b9bc682e20c050100c12fe2e0398 Mon Sep 17 00:00:00 2001 From: Alexander Kutsan Date: Tue, 7 May 2024 16:11:38 +0400 Subject: [PATCH 23/33] Directory Listing: Add UTF-8 Charset to the response Content type (#679) --- src/kemal/static_file_handler.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/kemal/static_file_handler.cr b/src/kemal/static_file_handler.cr index dbeca28..11a3057 100644 --- a/src/kemal/static_file_handler.cr +++ b/src/kemal/static_file_handler.cr @@ -57,7 +57,7 @@ module Kemal end send_file(context, file_path) elsif config.is_a?(Hash) && config.fetch("dir_listing", false) - context.response.content_type = "text/html" + context.response.content_type = "text/html;charset=UTF-8;" directory_listing(context.response, request_path, file_path) else call_next(context) From 6a29240bbecaa3a595804a4e67e30311b24543c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Serdar=20Dogruyol=20-=20Sedo=20=E3=82=BB=E3=83=89?= <990485+sdogruyol@users.noreply.github.com> Date: Fri, 10 May 2024 18:47:27 +0300 Subject: [PATCH 24/33] Use context instead of response in static_headers helper (#681) --- spec/static_file_handler_spec.cr | 6 +++--- src/kemal/config.cr | 2 +- src/kemal/helpers/helpers.cr | 10 +++++----- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/spec/static_file_handler_spec.cr b/spec/static_file_handler_spec.cr index 309e10f..5f8c029 100644 --- a/spec/static_file_handler_spec.cr +++ b/spec/static_file_handler_spec.cr @@ -141,11 +141,11 @@ 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| + headers = Proc(HTTP::Server::Context, String, File::Info, Void).new do |env, path, stat| if path =~ /\.html$/ - response.headers.add("Access-Control-Allow-Origin", "*") + env.response.headers.add("Access-Control-Allow-Origin", "*") end - response.headers.add("Content-Size", stat.size.to_s) + env.response.headers.add("Content-Size", stat.size.to_s) end static_headers(&headers) diff --git a/src/kemal/config.cr b/src/kemal/config.cr index df7330a..f728f57 100644 --- a/src/kemal/config.cr +++ b/src/kemal/config.cr @@ -23,7 +23,7 @@ module Kemal property app_name, host_binding, ssl, port, env, public_folder, logging, running property always_rescue, server : HTTP::Server?, extra_options, shutdown_message property serve_static : (Bool | Hash(String, Bool)) - property static_headers : (HTTP::Server::Response, String, File::Info -> Void)? + property static_headers : (HTTP::Server::Context, String, File::Info -> Void)? property? powered_by_header : Bool = true def initialize diff --git a/src/kemal/helpers/helpers.cr b/src/kemal/helpers/helpers.cr index 1b733c3..d8ef72b 100644 --- a/src/kemal/helpers/helpers.cr +++ b/src/kemal/helpers/helpers.cr @@ -134,7 +134,7 @@ def send_file(env : HTTP::Server::Context, path : String, mime_type : String? = filestat = File.info(file_path) attachment(env, filename, disposition) - Kemal.config.static_headers.try(&.call(env.response, file_path, filestat)) + Kemal.config.static_headers.try(&.call(env, file_path, filestat)) File.open(file_path) do |file| if env.request.method == "GET" && env.request.headers.has_key?("Range") @@ -250,13 +250,13 @@ end # Adds headers to `Kemal::StaticFileHandler`. This is especially useful for `CORS`. # # ``` -# static_headers do |response, filepath, filestat| +# static_headers do |env, filepath, filestat| # if filepath =~ /\.html$/ -# response.headers.add("Access-Control-Allow-Origin", "*") +# env.response.headers.add("Access-Control-Allow-Origin", "*") # end -# response.headers.add("Content-Size", filestat.size.to_s) +# env.response.headers.add("Content-Size", filestat.size.to_s) # end # ``` -def static_headers(&headers : HTTP::Server::Response, String, File::Info -> Void) +def static_headers(&headers : HTTP::Server::Context, String, File::Info -> Void) Kemal.config.static_headers = headers end From 5554d3d2f1b7496e850df48663c48765aa8af511 Mon Sep 17 00:00:00 2001 From: Sijawusz Pur Rahnama Date: Thu, 16 May 2024 19:36:02 +0200 Subject: [PATCH 25/33] Followup to #679 (#680) --- src/kemal/static_file_handler.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/kemal/static_file_handler.cr b/src/kemal/static_file_handler.cr index 11a3057..4f91766 100644 --- a/src/kemal/static_file_handler.cr +++ b/src/kemal/static_file_handler.cr @@ -57,7 +57,7 @@ module Kemal end send_file(context, file_path) elsif config.is_a?(Hash) && config.fetch("dir_listing", false) - context.response.content_type = "text/html;charset=UTF-8;" + context.response.content_type = "text/html; charset=utf-8" directory_listing(context.response, request_path, file_path) else call_next(context) From 1d46fd17d7f4e0fd75e7abce5deacbbdc79d14e3 Mon Sep 17 00:00:00 2001 From: Abdullah Alhusaini <44743015+a-alhusaini@users.noreply.github.com> Date: Wed, 24 Jul 2024 16:51:08 +0300 Subject: [PATCH 26/33] fix #683 (#684) --- src/kemal/ext/context.cr | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/kemal/ext/context.cr b/src/kemal/ext/context.cr index 545111f..2ed168d 100644 --- a/src/kemal/ext/context.cr +++ b/src/kemal/ext/context.cr @@ -14,7 +14,11 @@ class HTTP::Server end def params - @params ||= Kemal::ParamParser.new(@request, route_lookup.params) + if ws_route_found? + @params ||= Kemal::ParamParser.new(@request, ws_route_lookup.params) + else + @params ||= Kemal::ParamParser.new(@request, route_lookup.params) + end end def redirect(url : String | URI, status_code : Int32 = 302, *, body : String? = nil, close : Bool = true) From 0afbd129f5abacf204f8ca419b770bbef3e05445 Mon Sep 17 00:00:00 2001 From: Abdullah Alhusaini <44743015+a-alhusaini@users.noreply.github.com> Date: Thu, 25 Jul 2024 12:17:35 +0300 Subject: [PATCH 27/33] use Process.on_terminate to support SIGTERM termination. (#685) --- src/kemal.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/kemal.cr b/src/kemal.cr index 843f89e..98bf324 100644 --- a/src/kemal.cr +++ b/src/kemal.cr @@ -89,7 +89,7 @@ module Kemal end private def self.setup_trap_signal - Process.on_interrupt do + Process.on_terminate do log "#{Kemal.config.app_name} is going to take a rest!" if Kemal.config.shutdown_message Kemal.stop exit From 3243b8e0e03568ad3bd9f0ad6f445c871605b821 Mon Sep 17 00:00:00 2001 From: Abdullah Alhusaini <44743015+a-alhusaini@users.noreply.github.com> Date: Tue, 30 Jul 2024 12:44:38 +0300 Subject: [PATCH 28/33] clarify startup message in test mode (#687) --- spec/run_spec.cr | 4 ++-- src/kemal.cr | 8 ++++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/spec/run_spec.cr b/spec/run_spec.cr index ec89d23..a629eac 100644 --- a/spec/run_spec.cr +++ b/spec/run_spec.cr @@ -26,7 +26,7 @@ describe "Run" do end it "runs without a block being specified" do - run(<<-CR).should eq "[test] Kemal is ready to lead at http://0.0.0.0:3000\ntrue\n" + run(<<-CR).should contain "[test] Kemal is running in test mode." Kemal.config.env = "test" Kemal.run puts Kemal.config.running @@ -34,7 +34,7 @@ describe "Run" do end it "allows custom HTTP::Server bind" do - run(<<-CR).should eq "[test] Kemal is ready to lead at http://127.0.0.1:3000, http://0.0.0.0:3001\n" + run(<<-CR).should contain "[test] Kemal is running in test mode." Kemal.config.env = "test" Kemal.run do |config| server = config.server.not_nil! diff --git a/src/kemal.cr b/src/kemal.cr index 98bf324..bf7af65 100644 --- a/src/kemal.cr +++ b/src/kemal.cr @@ -66,8 +66,12 @@ module Kemal end def self.display_startup_message(config, server) - addresses = server.addresses.join ", " { |address| "#{config.scheme}://#{address}" } - log "[#{config.env}] #{config.app_name} is ready to lead at #{addresses}" + if config.env != "test" + addresses = server.addresses.join ", " { |address| "#{config.scheme}://#{address}" } + log "[#{config.env}] #{config.app_name} is ready to lead at #{addresses}" + else + log "[#{config.env}] #{config.app_name} is running in test mode. Server not listening" + end end def self.stop From 85fcbbee02c43b8893e7bea4a2c886305bde13ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Serdar=20Dogruyol=20-=20Sedo=20=E3=82=BB=E3=83=89?= <990485+sdogruyol@users.noreply.github.com> Date: Tue, 1 Oct 2024 10:11:52 +0300 Subject: [PATCH 29/33] Windows support (#690) Windows support --- .gitattributes | 2 ++ .github/workflows/ci.yml | 47 ++++++++++++++++++++++++++++---- spec/helpers_spec.cr | 1 + spec/run_spec.cr | 9 ++++-- src/kemal/config.cr | 2 +- src/kemal/static_file_handler.cr | 2 +- 6 files changed, 53 insertions(+), 10 deletions(-) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..4bf9126 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +*.cr text eol=lf +*.ecr text eol=lf \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 983dc67..f473bda 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,31 +11,66 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-latest, macos-latest] + os: [ubuntu-latest, macos-latest, windows-latest] crystal: [latest, nightly] runs-on: ${{ matrix.os }} steps: - name: Install Crystal - uses: oprypin/install-crystal@v1 + uses: crystal-lang/install-crystal@v1 with: crystal: ${{ matrix.crystal }} - name: Download source - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Install dependencies run: shards install - env: - SHARDS_OPTS: --ignore-crystal-version - name: Run specs run: | crystal spec - crystal spec --release --no-debug + + format: + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest] + crystal: [latest, nightly] + runs-on: ${{ matrix.os }} + + steps: + - name: Install Crystal + uses: crystal-lang/install-crystal@v1 + with: + crystal: ${{ matrix.crystal }} + + - name: Download source + uses: actions/checkout@v4 - name: Check formatting run: crystal tool format --check + + ameba: + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest] + crystal: [latest] + runs-on: ${{ matrix.os }} + + steps: + - name: Install Crystal + uses: crystal-lang/install-crystal@v1 + with: + crystal: ${{ matrix.crystal }} + + - name: Download source + uses: actions/checkout@v4 + + - name: Install dependencies + run: shards install - name: Run ameba linter run: bin/ameba + \ No newline at end of file diff --git a/spec/helpers_spec.cr b/spec/helpers_spec.cr index ec587bb..334d4cc 100644 --- a/spec/helpers_spec.cr +++ b/spec/helpers_spec.cr @@ -106,6 +106,7 @@ describe "Macros" do request = HTTP::Request.new("GET", "/") response = call_request_on_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 diff --git a/spec/run_spec.cr b/spec/run_spec.cr index a629eac..c15a7e9 100644 --- a/spec/run_spec.cr +++ b/spec/run_spec.cr @@ -38,8 +38,13 @@ describe "Run" do Kemal.config.env = "test" Kemal.run do |config| server = config.server.not_nil! - server.bind_tcp "127.0.0.1", 3000, reuse_port: true - server.bind_tcp "0.0.0.0", 3001, reuse_port: true + + {% if flag?(:windows) %} + server.bind_tcp "127.0.0.1", 3000 + {% else %} + server.bind_tcp "127.0.0.1", 3000, reuse_port: true + server.bind_tcp "0.0.0.0", 3001, reuse_port: true + {% end %} end CR end diff --git a/src/kemal/config.cr b/src/kemal/config.cr index f728f57..b079c7b 100644 --- a/src/kemal/config.cr +++ b/src/kemal/config.cr @@ -1,5 +1,5 @@ module Kemal - VERSION = {{ `shards version #{__DIR__}`.chomp.stringify }} + VERSION = {{ `shards version "#{__DIR__}"`.chomp.stringify }} # Stores all the configuration options for a Kemal application. # It's a singleton and you can access it like. diff --git a/src/kemal/static_file_handler.cr b/src/kemal/static_file_handler.cr index 4f91766..3b4cf0e 100644 --- a/src/kemal/static_file_handler.cr +++ b/src/kemal/static_file_handler.cr @@ -27,7 +27,7 @@ module Kemal return end - expanded_path = File.expand_path(request_path, "/") + expanded_path = request_path is_dir_path = if original_path.ends_with?('/') && !expanded_path.ends_with? '/' expanded_path = expanded_path + '/' true From dc031c6074c5466511ca4d7a2285a61159311f21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Sat, 12 Oct 2024 12:01:24 +0200 Subject: [PATCH 30/33] `crystal tool format` with Crystal 1.15.0-dev (#691) --- spec/init_handler_spec.cr | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/init_handler_spec.cr b/spec/init_handler_spec.cr index 20724c6..97397d3 100644 --- a/spec/init_handler_spec.cr +++ b/spec/init_handler_spec.cr @@ -6,7 +6,7 @@ describe "Kemal::InitHandler" do io = IO::Memory.new response = HTTP::Server::Response.new(io) context = HTTP::Server::Context.new(request, response) - Kemal::InitHandler::INSTANCE.next = ->(_context : HTTP::Server::Context) {} + Kemal::InitHandler::INSTANCE.next = ->(_context : HTTP::Server::Context) { } Kemal::InitHandler::INSTANCE.call(context) context.response.headers["Content-Type"].should eq "text/html" end @@ -16,7 +16,7 @@ describe "Kemal::InitHandler" do io = IO::Memory.new response = HTTP::Server::Response.new(io) context = HTTP::Server::Context.new(request, response) - Kemal::InitHandler::INSTANCE.next = ->(_context : HTTP::Server::Context) {} + Kemal::InitHandler::INSTANCE.next = ->(_context : HTTP::Server::Context) { } Kemal::InitHandler::INSTANCE.call(context) date = context.response.headers["Date"]?.should_not be_nil date = HTTP.parse_time(date).should_not be_nil From 2d8df1f11640a2aca5431fc714653de8815fc028 Mon Sep 17 00:00:00 2001 From: Serdar Dogruyol <990485+sdogruyol@users.noreply.github.com> Date: Sat, 12 Oct 2024 13:07:56 +0300 Subject: [PATCH 31/33] Update CHANGELOG --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ef7657..130192c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# 1.6.0 (12-10-2024) + +- Crystal 1.14.0 support :tada: +- Windows support [#690](https://github.com/kemalcr/kemal/pull/690). Thanks @sdogruyol :pray: +- Directory Listing: Add UTF-8 Charset to the response Content type [#679](https://github.com/kemalcr/kemal/pull/679). Thanks @alexkutsan @Sija :pray: +- Use context instead of response in static_headers helper [#681](https://github.com/kemalcr/kemal/pull/681). Thanks @sdogruyol :pray: + # 1.5.0 (10-04-2024) - Crystal 1.12.0 support :tada: From 75d5ef10465f1d42e232d0454ff926cf004e3d4f Mon Sep 17 00:00:00 2001 From: Serdar Dogruyol <990485+sdogruyol@users.noreply.github.com> Date: Sat, 12 Oct 2024 13:09:23 +0300 Subject: [PATCH 32/33] Bump version to 1.6.0 --- shard.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shard.yml b/shard.yml index 4223a97..8207d0d 100644 --- a/shard.yml +++ b/shard.yml @@ -1,5 +1,5 @@ name: kemal -version: 1.5.0 +version: 1.6.0 authors: - Serdar Dogruyol From 749c537e853af6f032b01cf2b91ae2e740340d62 Mon Sep 17 00:00:00 2001 From: Sijawusz Pur Rahnama Date: Mon, 28 Oct 2024 11:55:56 +0100 Subject: [PATCH 33/33] Bump exception_page shard to version ~> 0.5.0 (#693) --- shard.yml | 2 +- src/kemal/helpers/templates.cr | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/shard.yml b/shard.yml index 8207d0d..d0371c4 100644 --- a/shard.yml +++ b/shard.yml @@ -10,7 +10,7 @@ dependencies: version: ~> 0.4.0 exception_page: github: crystal-loot/exception_page - version: ~> 0.4.1 + version: ~> 0.5.0 development_dependencies: ameba: diff --git a/src/kemal/helpers/templates.cr b/src/kemal/helpers/templates.cr index 0468d38..1769bd5 100644 --- a/src/kemal/helpers/templates.cr +++ b/src/kemal/helpers/templates.cr @@ -26,7 +26,7 @@ def render_500(context, exception, verbosity) context.response.status_code = 500 template = if verbosity - Kemal::ExceptionPage.for_runtime_exception(context, exception).to_s + Kemal::ExceptionPage.new(context, exception).to_s else Kemal::ExceptionPage.for_production_exception end