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/.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/CHANGELOG.md b/CHANGELOG.md
index 3bc639b..130192c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,16 +1,45 @@
+# 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:
+- 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:
+- 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:
+- 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:
 - 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)
 
@@ -18,7 +47,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:
@@ -31,7 +62,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)
 
@@ -57,88 +88,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)
 
@@ -156,71 +182,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)
 
@@ -228,16 +252,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.
@@ -246,40 +268,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)
 
@@ -288,133 +305,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`.
diff --git a/shard.yml b/shard.yml
index 69a311a..d0371c4 100644
--- a/shard.yml
+++ b/shard.yml
@@ -1,5 +1,5 @@
 name: kemal
-version: 1.2.0
+version: 1.6.0
 
 authors:
   - Serdar Dogruyol <dogruyolserdar@gmail.com>
@@ -10,12 +10,11 @@ dependencies:
     version: ~> 0.4.0
   exception_page:
     github: crystal-loot/exception_page
-    version: ~> 0.2.0
+    version: ~> 0.5.0
 
 development_dependencies:
   ameba:
     github: crystal-ameba/ameba
-    version: ~> 1.0
 
 crystal: ">= 0.36.0"
 
diff --git a/spec/config_spec.cr b/spec/config_spec.cr
index daa6849..11aa6d3 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
@@ -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/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/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..334d4cc 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
 
@@ -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
@@ -150,7 +151,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/spec/init_handler_spec.cr b/spec/init_handler_spec.cr
index 601bbc1..97397d3 100644
--- a/spec/init_handler_spec.cr
+++ b/spec/init_handler_spec.cr
@@ -6,11 +6,23 @@ 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
 
+  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/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/spec/run_spec.cr b/spec/run_spec.cr
index ec89d23..c15a7e9 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,12 +34,17 @@ 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!
-        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/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/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/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.cr b/src/kemal.cr
index 9222fe6..bf7af65 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
@@ -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
@@ -89,7 +93,7 @@ module Kemal
   end
 
   private def self.setup_trap_signal
-    Signal::INT.trap do
+    Process.on_terminate do
       log "#{Kemal.config.app_name} is going to take a rest!" if Kemal.config.shutdown_message
       Kemal.stop
       exit
diff --git a/src/kemal/config.cr b/src/kemal/config.cr
index 23372dd..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.
@@ -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 static_headers : (HTTP::Server::Context, String, File::Info -> Void)?
+    property? powered_by_header : Bool = true
 
     def initialize
       @app_name = "Kemal"
@@ -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
@@ -153,13 +159,13 @@ 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
 
-  def self.config
+  def self.config(&)
     yield Config::INSTANCE
   end
 
diff --git a/src/kemal/ext/context.cr b/src/kemal/ext/context.cr
index 70787b6..2ed168d 100644
--- a/src/kemal/ext/context.cr
+++ b/src/kemal/ext/context.cr
@@ -9,16 +9,20 @@ 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
 
     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, 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
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/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/exceptions.cr b/src/kemal/helpers/exceptions.cr
index 53520b3..cda5e59 100644
--- a/src/kemal/helpers/exceptions.cr
+++ b/src/kemal/helpers/exceptions.cr
@@ -13,8 +13,9 @@ module Kemal::Exceptions
   end
 
   class CustomException < Exception
-    def initialize(context : HTTP::Server::Context)
-      super "Rendered error with #{context.response.status_code}"
+    def initialize(@context : HTTP::Server::Context, message : String? = nil)
+      message ||= "Rendered error with #{context.response.status_code}"
+      super message
     end
   end
 end
diff --git a/src/kemal/helpers/helpers.cr b/src/kemal/helpers/helpers.cr
index 11a6b62..d8ef72b 100644
--- a/src/kemal/helpers/helpers.cr
+++ b/src/kemal/helpers/helpers.cr
@@ -1,15 +1,16 @@
-{% 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 %}
 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
@@ -48,13 +49,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 +72,6 @@ end
 # ```
 def logger(logger : Kemal::BaseLogHandler)
   Kemal.config.logger = logger
-  Kemal.config.add_handler logger
 end
 
 # Enables / Disables static file serving.
@@ -134,40 +134,45 @@ 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")
       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
@@ -245,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
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
diff --git a/src/kemal/init_handler.cr b/src/kemal/init_handler.cr
index 881325b..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.
@@ -7,8 +9,9 @@ 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")
+      context.response.headers.add "Date", HTTP.format_time(Time.utc)
       call_next context
     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
diff --git a/src/kemal/route_handler.cr b/src/kemal/route_handler.cr
index d256403..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
@@ -57,7 +60,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)
diff --git a/src/kemal/static_file_handler.cr b/src/kemal/static_file_handler.cr
index dbeca28..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
@@ -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)