Compare commits

...

17 commits

Author SHA1 Message Date
99b1e759ba Merge remote-tracking branch 'upstream/master' 2022-08-07 15:55:22 -03:00
Anton Maminov
f5d767fe7e
Fix static file handler for Crystal 1.6.0 (#644) 2022-08-04 12:42:18 +03:00
Serdar Dogruyol
4aa28c423c Enable GH sponsors 2022-07-30 11:18:58 +03:00
Serdar Dogruyol
707e61641c Bump version to 1.2.0 2022-07-30 11:18:58 +03:00
Serdar Dogruyol
c993a05731 Add changelog for 1.2.0 2022-07-30 11:18:58 +03:00
Oleksii Vasyliev
1a45f54c6c
Disable signal trap for usage kemal with other tools (#642) 2022-07-16 12:44:40 +03:00
Russell Smith
05d55540b9
Enable option for index.html to be a directories default (#640) 2022-06-30 18:00:51 +03:00
Serdar Dogruyol
268e501a63 fix Crystal 1.5.0 warnings 2022-06-29 13:31:28 +03:00
Serdar Dogruyol
f8fc8ce8c8 fix ameba warnings 2022-06-29 13:29:50 +03:00
Chao Yang
9bd24caf7e
Closes response by default in HTTP::Server::Context#redirect (#641) 2022-06-29 13:25:40 +03:00
Serdar Dogruyol - Sedo セド
317d086b4c
fix content_for failing to capture the correct block input (#639) 2022-06-27 12:28:13 +03:00
Alexander Zhou
d53d253620 Eliminated several seconds of delay when loading big mp4 file 2022-05-31 17:14:59 +03:00
Johannes Müller
f706c7877d Update ameba to 1.0 2022-05-31 17:14:59 +03:00
Serdar Dogruyol
1d54971efa Bump version to 1.1.2 2022-02-24 17:56:54 +03:00
matthewmcgarvey
d6dc893052 Fix content rendering 2022-02-24 17:49:19 +03:00
Serdar Dogruyol
59720fbd16 Bump version to 1.1.1 2022-02-22 17:29:53 +03:00
Samantaz Fox
3d2d30db93 Ignore HTTP::Server::Response patching for crystal >= 1.3.0
Closes #627
2022-02-22 17:10:04 +03:00
18 changed files with 145 additions and 55 deletions

2
.github/FUNDING.yml vendored
View file

@ -1,6 +1,6 @@
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
github: sdogruyol
patreon: sdogruyol
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username

View file

@ -1,3 +1,24 @@
# 1.2.0 (07-07-2022)
- Crystal 1.5.0 support :tada:
- Eliminated several seconds of delay when loading big mp4 file. Thanks @Athlon64 :pray:
- Fix content_for failing to capture the correct block input [#639](https://github.com/kemalcr/kemal/pull/639). Thanks @sdogruyol :pray:
- Closes response by default in HTTP::Server::Context#redirect [#641](https://github.com/kemalcr/kemal/pull/641). Thanks @cyangle :pray:
- Enable option for index.html to be a directories default [#640](https://github.com/kemalcr/kemal/pull/640). Thanks @ukd1 :pray:
You can enable it via
```crystal
serve_static({"dir_index" => true})
```
# 1.1.2 (24-02-2022)
- Fix content rendering [#631](https://github.com/kemalcr/kemal/pull/631). Thanks @matthewmcgarvey :pray:
# 1.1.1 (22-02-2022)
- Ignore HTTP::Server::Response patching for crystal >= 1.3.0 [#628](https://github.com/kemalcr/kemal/pull/628). Thanks @SamantazFox :pray:
# 1.1.0 (02-09-2021)
- You can now set your own application name for startup message [#606](https://github.com/kemalcr/kemal/pull/606). Thanks @aravindavk :pray:

View file

@ -1,5 +1,5 @@
name: kemal
version: 1.1.0
version: 1.2.0
authors:
- Serdar Dogruyol <dogruyolserdar@gmail.com>
@ -15,7 +15,7 @@ dependencies:
development_dependencies:
ameba:
github: crystal-ameba/ameba
version: ~> 0.14.0
version: ~> 1.0
crystal: ">= 0.36.0"

View file

@ -1,5 +1,5 @@
Hello <%= name %>
<% content_for "custom" do %>
<h1>Hello from otherside</h1>
<% content_for "meta" do %>
<title>Kemal Spec</title>
<% end %>

View file

@ -1,6 +1,8 @@
<html>
<head>
<%= yield_content "meta" %>
</head>
<body>
<%= content %>
<%= yield_content "custom" %>
</body>
</html>

View file

@ -1,8 +1,10 @@
<html>
<head>
<%= yield_content "meta" %>
</head>
<body>
<%= content %>
<%= yield_content "custom" %>
<%= var1 %>
<%= var2 %>
<%= var1 %>
<%= var2 %>
</body>
</html>

View file

@ -143,4 +143,40 @@ describe "Kemal::RouteHandler" do
client_response.body.should eq("Redirecting to /login")
client_response.headers.has_key?("Location").should eq(true)
end
it "redirects and closes response in before filter" do
filter_handler = Kemal::FilterHandler.new
filter_handler._add_route_filter("GET", "/", :before) do |env|
env.redirect "/login"
end
Kemal.config.add_filter_handler(filter_handler)
get "/" do
"home page"
end
request = HTTP::Request.new("GET", "/")
client_response = call_request_on_app(request)
client_response.status_code.should eq(302)
client_response.body.should eq("")
client_response.headers.has_key?("Location").should eq(true)
end
it "redirects in before filter without closing response" do
filter_handler = Kemal::FilterHandler.new
filter_handler._add_route_filter("GET", "/", :before) do |env|
env.redirect "/login", close: false
end
Kemal.config.add_filter_handler(filter_handler)
get "/" do
"home page"
end
request = HTTP::Request.new("GET", "/")
client_response = call_request_on_app(request)
client_response.status_code.should eq(302)
client_response.body.should eq("home page")
client_response.headers.has_key?("Location").should eq(true)
end
end

View file

@ -4,8 +4,8 @@ require "../src/*"
include Kemal
class CustomLogHandler < Kemal::BaseLogHandler
def call(env)
call_next env
def call(context)
call_next(context)
end
def write(message)

View file

@ -23,6 +23,15 @@ describe Kemal::StaticFileHandler do
response.body.should eq(File.read("#{__DIR__}/static/dir/test.txt"))
end
it "should serve the 'index.html' file when a directory is requested and index serving is enabled" do
serve_static({"dir_index" => true})
response = handle HTTP::Request.new("GET", "/dir/")
response.status_code.should eq(200)
response.headers["Content-Type"].should eq "text/html"
response.headers["Etag"].should contain "W/\""
response.body.should eq(File.read("#{__DIR__}/static/dir/index.html"))
end
it "should respond with 304 if file has not changed" do
response = handle HTTP::Request.new("GET", "/dir/test.txt")
response.status_code.should eq(200)

View file

@ -22,7 +22,7 @@ describe "Views" do
end
request = HTTP::Request.new("GET", "/view/world")
client_response = call_request_on_app(request)
client_response.body.should contain("Hello world")
client_response.body.strip.should eq("<html>Hello world\n</html>")
end
it "renders layout" do
@ -56,7 +56,17 @@ describe "Views" do
end
request = HTTP::Request.new("GET", "/view/world")
client_response = call_request_on_app(request)
client_response.body.should contain("Hello world")
client_response.body.should contain("<h1>Hello from otherside</h1>")
client_response.body.scan("Hello world").size.should eq(1)
client_response.body.should contain("<title>Kemal Spec</title>")
end
it "does not render content_for that was not yielded" do
get "/view/:name" do |env|
name = env.params.url["name"]
render "#{__DIR__}/asset/hello_with_content_for.ecr", "#{__DIR__}/asset/layout.ecr"
end
request = HTTP::Request.new("GET", "/view/world")
client_response = call_request_on_app(request)
client_response.body.should_not contain("<h1>Hello from otherside</h1>")
end
end

View file

@ -7,18 +7,18 @@ require "./kemal/helpers/*"
module Kemal
# Overload of `self.run` with the default startup logging.
def self.run(port : Int32?, args = ARGV)
self.run(port, args) { }
def self.run(port : Int32?, args = ARGV, trap_signal : Bool = true)
self.run(port, args, trap_signal) { }
end
# Overload of `self.run` without port.
def self.run(args = ARGV)
self.run(nil, args: args)
def self.run(args = ARGV, trap_signal : Bool = true)
self.run(nil, args: args, trap_signal: trap_signal)
end
# Overload of `self.run` to allow just a block.
def self.run(args = ARGV, &block)
self.run(nil, args: args, &block)
self.run(nil, args: args, trap_signal: true, &block)
end
# The command to run a `Kemal` application.
@ -27,7 +27,7 @@ module Kemal
#
# To use custom command line arguments, set args to nil
#
def self.run(port : Int32? = nil, args = ARGV, &block)
def self.run(port : Int32? = nil, args = ARGV, trap_signal : Bool = true, &block)
Kemal::CLI.new args
config = Kemal.config
config.setup
@ -36,7 +36,7 @@ module Kemal
# Test environment doesn't need to have signal trap and logging.
if config.env != "test"
setup_404
setup_trap_signal
setup_trap_signal if trap_signal
end
server = config.server ||= HTTP::Server.new(config.handlers)

View file

@ -31,7 +31,7 @@ module Kemal
@host_binding = "0.0.0.0"
@port = 3000
@env = ENV["KEMAL_ENV"]? || "development"
@serve_static = {"dir_listing" => false, "gzip" => true}
@serve_static = {"dir_listing" => false, "gzip" => true, "dir_index" => false}
@public_folder = "./public"
@logging = true
@logger = nil

View file

@ -17,10 +17,11 @@ class HTTP::Server
@params ||= Kemal::ParamParser.new(@request, route_lookup.params)
end
def redirect(url : String, status_code : Int32 = 302, *, body : String? = nil)
def redirect(url : String, status_code : Int32 = 302, *, body : String? = nil, close : Bool = true)
@response.headers.add "Location", url
@response.status_code = status_code
@response.print(body) if body
@response.close if close
end
def route

View file

@ -1,3 +1,7 @@
# This override collides with the new stdlib of Crystal 1.3
# See https://github.com/kemalcr/kemal/issues/627 for more details
{{ skip_file if compare_versions(Crystal::VERSION, "1.3.0") >= 0 }}
class HTTP::Server::Response
class Output
def close

View file

@ -27,8 +27,8 @@ module Kemal
end
end
def call(env : HTTP::Server::Context)
call_next(env)
def call(context : HTTP::Server::Context)
call_next(context)
end
# Processes the path based on `only` paths which is a `Array(String)`.

View file

@ -216,20 +216,7 @@ private def multipart(file, env : HTTP::Server::Context)
env.response.headers["Accept-Ranges"] = "bytes"
env.response.headers["Content-Range"] = "bytes #{startb}-#{endb}/#{fileb}" # MUST
if startb > 1024
skipped = 0_i64
# file.skip only accepts values less or equal to 1024 (buffer size, undocumented)
until (increase_skipped = skipped + 1024_i64) > startb
file.skip(1024)
skipped = increase_skipped
end
if (skipped_minus_startb = skipped - startb) > 0
file.skip skipped_minus_startb
end
else
file.skip(startb)
end
file.seek(startb)
IO.copy(file, env.response, content_length)
else
env.response.content_length = fileb

View file

@ -1,4 +1,4 @@
CONTENT_FOR_BLOCKS = Hash(String, Tuple(String, Proc(String))).new
CONTENT_FOR_BLOCKS = Hash(String, Tuple(String, Proc(Nil))).new
# `content_for` is a set of helpers that allows you to capture
# blocks inside views to be rendered later during the request. The most
@ -34,13 +34,7 @@ CONTENT_FOR_BLOCKS = Hash(String, Tuple(String, Proc(String))).new
# layout, inside the <head> tag, and each view can call `content_for`
# setting the appropriate set of tags that should be added to the layout.
macro content_for(key, file = __FILE__)
%proc = ->() {
__view_io__ = IO::Memory.new
{{ yield }}
__view_io__.to_s
}
CONTENT_FOR_BLOCKS[{{key}}] = Tuple.new {{file}}, %proc
CONTENT_FOR_BLOCKS[{{key}}] = Tuple.new {{file}}, ->() { {{ yield }} }
nil
end
@ -49,7 +43,14 @@ macro yield_content(key)
if CONTENT_FOR_BLOCKS.has_key?({{key}})
__caller_filename__ = CONTENT_FOR_BLOCKS[{{key}}][0]
%proc = CONTENT_FOR_BLOCKS[{{key}}][1]
%proc.call if __content_filename__ == __caller_filename__
if __content_filename__ == __caller_filename__
%old_content_io, content_io = content_io, IO::Memory.new
%proc.call
%result = content_io.to_s
content_io = %old_content_io
%result
end
end
end
@ -60,10 +61,12 @@ end
# ```
macro render(filename, layout)
__content_filename__ = {{filename}}
io = IO::Memory.new
content = ECR.embed {{filename}}, io
ECR.embed {{layout}}, io
io.to_s
content_io = IO::Memory.new
ECR.embed {{filename}}, content_io
content = content_io.to_s
layout_io = IO::Memory.new
ECR.embed {{layout}}, layout_io
layout_io.to_s
end
# Render view with the given filename.

View file

@ -36,7 +36,7 @@ module Kemal
end
file_path = File.join(@public_dir, expanded_path)
is_dir = Dir.exists? file_path
is_dir = Dir.exists?(file_path)
if request_path != expanded_path
redirect_to context, expanded_path
@ -44,8 +44,19 @@ module Kemal
redirect_to context, expanded_path + '/'
end
if Dir.exists?(file_path)
if config.is_a?(Hash) && config["dir_listing"] == true
if is_dir
if config.is_a?(Hash) && config.fetch("dir_index", false) && File.exists?(File.join(file_path, "index.html"))
file_path = File.join(@public_dir, expanded_path, "index.html")
last_modified = modification_time(file_path)
add_cache_headers(context.response.headers, last_modified)
if cache_request?(context, last_modified)
context.response.status_code = 304
return
end
send_file(context, file_path)
elsif config.is_a?(Hash) && config.fetch("dir_listing", false)
context.response.content_type = "text/html"
directory_listing(context.response, request_path, file_path)
else
@ -64,5 +75,9 @@ module Kemal
call_next(context)
end
end
private def modification_time(file_path)
File.info(file_path).modification_time
end
end
end