- Add modular
Kemal::Routerwith namespaced routing, scoped filters, WebSocket support and flexible mounting while keeping the existing DSL fully compatible #731. Thanks @sdogruyol π
require "kemal"
api = Kemal::Router.new
api.namespace "/users" do
get "/" do |env|
env.json({users: ["alice", "bob"]})
end
get "/:id" do |env|
env.text "user #{env.params.url["id"]}"
end
end
mount "/api/v1", api
Kemal.run- Add
usekeyword for registering global and path-specific middleware, including support for arrays and insertion at a specific position in the handler chain #734. Thanks @sdogruyol π
require "kemal"
# Path-specific middlewares for /api routes
use "/api", [CORSHandler.new, AuthHandler.new]
get "/" do
"Public home"
end
get "/api/users" do |env|
env.json({users: ["alice", "bob"]})
end
Kemal.run- Enhance response helpers to provide chainable JSON/HTML/text/XML helpers,
HTTP::Statussupport and the ability to halt execution from a chained response for concise API error handling #733, #735, #736. Thanks @sdogruyol and @mamantoha π
require "kemal"
get "/users" do |env|
# Default JSON response
env.json({users: ["alice", "bob"]})
end
post "/users" do |env|
# Symbol-based HTTP::Status and chained JSON
env.status(:created).json({id: 1, created: true})
end
get "/admin" do |env|
# Halt immediately with HTML response
halt env.status(403).html("<h1>Forbidden</h1>")
end
get "/api/users" do |env|
# Custom content type (JSON:API)
env.json({data: ["alice", "bob"]}, content_type: "application/vnd.api+json")
end
Kemal.run- Ensure global wildcard filters always execute while keeping namespace filters isolated to their routes #737. Thanks @mamantoha π
- Fix CLI SSL validation and expand CLI option parsing specs #738. Thanks @sdogruyol π
- Make route LRU cache concurrency-safe with Mutex #739. Thanks @sdogruyol π
- Add
raw_bodyto ParamParser for multi-handler body access (e.g. kemal-session) #740. Thanks @sdogruyol π
post "/" do |env|
raw = env.params.raw_body # raw body, multiple handlers can call it
env.params.body["name"] # parsed body
end- Fix OverrideMethodHandler route cache bug when using
_methodoverride #741, #742. Thanks @skojin and @sdogruyol π
- Crystal 1.19.0 support π
- (SECURITY) Limit maximum request body size to avoid DoS attacks #730. Thanks @sdogruyol π
- Optimize JSON parameter parsing by directly using the request body IO. Thanks @sdogruyol π
- Enhance HEAD request handling by caching GET route lookups and optimize path construction using string interpolation for improved performance #728. Thanks @sdogruyol π
- Improve error messages #726. Thanks @sdogruyol π
- Optimize route and websocket lookups by caching results to reduce redundant processing in the HTTP server context #725. Thanks @sdogruyol π
- Replace full-flush Route cache with LRU and add a configurable max cache size #724. Thanks @sdogruyol π
- Refactor #719. Thanks @sdogruyol π
- Improve Kemal test suite. Thanks @sdogruyol π
- Move Kemal::Handler logic into separate module #717. Thanks @syeopite π
- Refactor server binding logic to avoid binding in test environment #719. Thanks @sdogruyol π
- Improve
StaticFileHandlerto align with latest Crystal implementation #711. Thanks @sdogruyol π
- (SECURITY) Fix a Path Traversal Security issue in
StaticFileHandler. See for more details. Thanks a lot @ahmetumitbayram π - Crystal 1.16.0 support π
- Add ability to add handlers for raised exceptions #688. Thanks @syeopite π
require "kemal"
class NewException < Exception
end
get "/" do | env |
raise NewException.new()
end
error NewException do | env |
"An error occured!"
end
Kemal.run- Add
all_filesmethod toparamsto support multiple file uploads in names ending with[]#701. Thanks @sdogruyol π
images = env.params.all_files["images[]"]?- Embrace Crystal standard Log for logging #705. Thanks @hugopl π
- Cleanup temporary files for file uploads #707. Thanks @sdogruyol π
- Implement multiple partial ranges #708. Thanks @sdogruyol π
- Crystal 1.14.0 support π
- Windows support #690. Thanks @sdogruyol π
- Directory Listing: Add UTF-8 Charset to the response Content type #679. Thanks @alexkutsan @Sija π
- Use context instead of response in static_headers helper #681. Thanks @sdogruyol π
- Crystal 1.12.0 support π
- Allow HTTP::Server::Context#redirect to take an URL #659. Thanks @xendk π
- Bump
exception_pagedependency #669. Thanks @Sija π - Add message support to
Kemal::Exceptions::CustomException#671. Thanks @sdogruyol π - Add
Dateheader to HTTP responses #676. Thanks @Sija π
- Crystal 1.8.0 support π
- Fix multiple logger handlers when custom logger is used #653. Thanks @aravindavk π
- Add
Kemal::OverrideMethodHandler#651. Thanks @sdogruyol π HeadRequestHandler: run GET handler and don't return the body #655. Thanks @compumike π
- Crystal 1.6.0 support π
- Disable signal trap for usage Kemal with other tools #642. Thanks @le0pard π
- Bump exception_page shard to v0.3.0 #645. Thanks @Sija π
- (Security) Omitting filters fix for lowercase methods requests #647. Thanks @sdogruyol @SlayerShadow π
-
Crystal 1.5.0 support π
-
Eliminated several seconds of delay when loading big mp4 file. Thanks @Athlon64 π
-
Fix
content_forfailing to capture the correct block input #639. Thanks @sdogruyol π -
Closes response by default in
HTTP::Server::Context#redirect#641. Thanks @cyangle π -
Enable option for
index.htmlto be a directories default #640. Thanks @ukd1 πYou can enable it via:
serve_static({"dir_index" => true})
- Fix content rendering #631. Thanks @matthewmcgarvey π
- Remove Kilt #618. Thanks @sdogruyol π
- Ignore
HTTP::Server::Responsepatching for crystal >= 1.3.0 #628. Thanks @SamantazFox π
- You can now set your own application name for startup message #606. Thanks @aravindavk π
- Add array of paths support for before/after filters #605. Thanks @sdogruyol π
- Fixed executing filters when before and after is defined at the same time #612. Thanks @mamantoha π
- Set content type to text/html for 500 exceptions #616. Thanks @sdogruyol π
- Crystal 1.0.0 support π
- Update Radix to use latest 0.4.0 #596. Thanks @luislavena π
- Use latest version of Ameba dependency (dev) #597. Thanks @luislavena π
- Fix
StaticFileHandlerfailing spec #599. Thanks @jinn999 π
- Crystal 0.35.x support π Thanks @bcardiff π
- Fix issues with responding with long strings #576. Thanks @mamantoha π
- Fix broken WebSocket support in 0.35.0 #577. Thanks @mamantoha π
- Allow to set optional response body on redirects #561. Thanks @mamantoha π
- Fix process request when a response already closed #550. Thanks @mamantoha π
- Switch to new Ameba repository #549. Thanks @mamantoha π
- Check for
KEMAL_ENVvariable already inConfig#initialize#552. Thanks @Sija π - Cleanup Ameba warnings #551. Thanks @Sija π
- Flush io buffer after each write to log #554. Thanks @mang π
- Crystal 0.30.0 support π #548 and #544. Thanks @bcardiff and @straight-shoota π
- Add support for serving files greater than 2^31 bytes #546. Thanks @omarroth π
- Properly measure request time using
Time.monotonic#527. Thanks @spinscale π
-
Add option to config to parse or not command line parameters #483. Thanks @diegogub π
-
Allow to set filename for
send_file#512. Thanks @mamantoha πsend_file env, "./asset/image.jpeg", filename: "image.jpg"
-
Set
status_codebefore response #513. Thanks @mamantohoa π -
Use Crystal MIME registry. #516 Thanks @Sija π
- Fix
params.filesmemoization #503. Thanks @mamantoha π
- 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.
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"
endTo test
curl -F "image1=@/Users/serdar/Downloads/kemal.png" http://localhost:3000/upload
- Cache HTTP routes to increase performance π #493
-
[breaking change] Removed
env.params.files. You can use Crystal's built-inHTTP::FormData.parseinstead: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) end "Upload OK" end end
-
[breaking change] From now on to access dynamic url params in a WebSocket route you have to use:
ws "/:id" do |socket, context| id = context.ws_route_lookup.params["id"] end
-
[breaking change] Removed
_methodmagic param. -
Added new exception page #466. Thanks @mamantoha π
-
Support custom port binding. Thanks @straight-shoota π
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 0.25.0 support π
- Add
Kemal::Context.get?to safely access context storage π - [Security] Don't serve 404 image dynamically π
- Disable
X-Powered-Byheader #449. Thanks @Blacksmoke16 π
- Crystal 0.24.1 support π
- Only return string from route.#408 thanks @crisward π
- Don't crash on empty path when compiled in --release. #407 thanks @crisward π
- Rename
Kemal::CommonLogHandlertoKemal::LogHandlerandKemal::CommonExceptionHandlertoKemal::ExceptionHandler. - Allow videos to be opened with correct mime type. #406 thanks @crisward π
- Add webm mime type.#413 thanks @reindeer-cafe π
-
Dynamically insert handlers πͺ Fixes #376.
-
Add context to WebSocket. This allows one to use
HTTP::Server::Contextinwsdeclarations π Fixes #349.ws "/:room_name" do |socket, env| env.params.url["room_name"] end
-
Add support for customizing the headers of built-in
Kemal::StaticFileHandlerπ¨ Useful for supportingCORSfor single page applications π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
-
Allow
%win Handler macros #385. Thanks @will π -
Security:
X-Content-Type-Options: nosnifffor static files. Fixes #379. Thanks @crisward π -
Performance: Remove tempfile management to OS. This brings 10-15% performance boost to Kemal π
-
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
Kemalhandlers or go wild, it's all up to you β# 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.
# This adds MyCustomHandler instance to 1 position. # Be aware that the index starts from 0. add_handler MyCustomHandler.new, 1
-
Updated Kilt to v0.4.0.
-
Make
RouteaStruct. This improves the performance of route declarations.
- Return no body for head route fixes #323. (thanks @crisward)
- Update Radix to
v0.3.8. (thanks @waghanza) - User defined context store types. (thanks @neovitange)
class User
property name
end
add_context_storage_type(User)- Prevent
send_filereturning filesize. (thanks @crisward) - Don't call setup in
config#add_filter_handlerfixes #338.
- Remove
Gzip::Headermonkey patch since it's fixed inCrystal 0.21.1.
- Fix Gzip in Kemal Seems broken for static files #316. This was caused by
Gzip::WriterinCrystal 0.21.0and currently mitigated by monkey patchingGzip::Header.
- Crystal 0.21.0 support
- Drop
multipart.crdependency.multipartsupport is now built-into Crystal <3 - Since Crystal 0.21.0 comes built-in with
multipartthere are some improvements and deprecations. metahas been removed fromFileUploadand 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.creation_time: Creation time of the file upload.modification_time: Last Modification time of the file upload.read_time: Read time of the file upload.size: Size of the file upload.
-
Simpler file upload. File uploads can now be access from
HTTP::Server::Contextlikeenv.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 oftmpfile.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
image1and saving it underpublic/uploads.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
To test
curl -F "image1=@/Users/serdar/Downloads/kemal.png" http://localhost:3000/upload -
RF7233 support a.k.a file streaming. #299 (thanks @denysvitali)
-
Update Radix to 0.3.7. Fixes #293
-
Configurable startup / shutdown logging. #291 and #292 (thanks @twisterghost).
- Update multipart.cr to 0.1.2. Fixes #285 related to multipart.cr
- Support for Crystal 0.20.3
- Add
Kemal.stop. Fixes #269. HTTP::Handleris not a class anymore, it's a module. See https://github.com/crystal-lang/crystal/releases/tag/0.20.3
- Handle missing 404 image. Fixes #263
- Remove basic auth middleware from core and move to kemalcr/kemal-basic-auth.
- Use
body.gets_to_endforparse_json. Fixes #260. - Update Radix to 0.3.5 and lock pessimistically. (thanks @luislavena)
- Treat
HTTP::Requestbody as anIO. Fixes #257
-
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.Which is illustrated as:
Request -> Middleware -> Filter -> Route -
Rename
return_withashalt. -
Route declaration must start with
/. Fixes #242 -
Set default exception
Content-Typetotext/html. Fixes #202 -
Add
onlyandexcludepaths forKemal::Handler. This change requires that all handlers must inherit fromKemal::Handler.For example this handler will only work on
/path. By default the HTTP method isGET.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." end end
The handlers using
excludewill work on the paths that isn't specified. For example this handler will work on any routes other than/.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." end end
-
Close response on
halt. (thanks @samueleaton). -
Update Radix to
v0.3.4. -
errorhandler now also yields error. For example you can get the error mesasage like:error 500 do |env, err| err.message end
-
Update
multipart.crtov0.1.1
-
Improved Multipart support with more info on parsed files.
parse_multipart(env)now yields anUploadFileobject which has the following properties:field,data,metaandheaders.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
-
Multipart support <3 (thanks @RX14). Now you can handle file uploads.
post "/upload" do |env| parse_multipart(env) do |field, data| image1 = data if field == "image1" image2 = data if field == "image2" "Upload complete" end end
-
Make session configurable. Now you can specify session name and expire time with:
Kemal.config.session["name"] = "your_app" Kemal.config.session["expire_time"] = 48.hours
-
Session now supports more types. (
String,Int32,Float64,Bool) -
Add
gziphelper to enable / disable gzip compression on responses. -
Static file caching with etag and gzip (thanks @crisward)
-
Kemal.runnow accepts port to listen.
- Don't forget to
call_nextonNullLogHandler
- Add context store.
KEMAL_ENVrespects toKemal.config.envand needs to be explicitly set.Kemal::InitHandleris introduced. Adds initial configuration, headers likeX-Powered-By.- Add
send_fileto helpers. - Add mime types.
- Fix parsing JSON params when "charset" is present in
Content-Typeheader. - Use http-only cookie for session.
- Inject
STDOUTby default inCommonLogHandler.