Compare commits
No commits in common. "99b1e759ba429f10095ab06a2977636fd3392721" and "eb1832acaf1e9ce1014b99d3bd56ec8522d0df91" have entirely different histories.
99b1e759ba
...
eb1832acaf
18 changed files with 55 additions and 145 deletions
2
.github/FUNDING.yml
vendored
2
.github/FUNDING.yml
vendored
|
@ -1,6 +1,6 @@
|
|||
# These are supported funding model platforms
|
||||
|
||||
github: sdogruyol
|
||||
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||
patreon: sdogruyol
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
|
|
21
CHANGELOG.md
21
CHANGELOG.md
|
@ -1,24 +1,3 @@
|
|||
# 1.2.0 (07-07-2022)
|
||||
|
||||
- Crystal 1.5.0 support :tada:
|
||||
- Eliminated several seconds of delay when loading big mp4 file. Thanks @Athlon64 :pray:
|
||||
- Fix content_for failing to capture the correct block input [#639](https://github.com/kemalcr/kemal/pull/639). Thanks @sdogruyol :pray:
|
||||
- Closes response by default in HTTP::Server::Context#redirect [#641](https://github.com/kemalcr/kemal/pull/641). Thanks @cyangle :pray:
|
||||
- Enable option for index.html to be a directories default [#640](https://github.com/kemalcr/kemal/pull/640). Thanks @ukd1 :pray:
|
||||
|
||||
You can enable it via
|
||||
|
||||
```crystal
|
||||
serve_static({"dir_index" => true})
|
||||
```
|
||||
|
||||
# 1.1.2 (24-02-2022)
|
||||
|
||||
- Fix content rendering [#631](https://github.com/kemalcr/kemal/pull/631). Thanks @matthewmcgarvey :pray:
|
||||
|
||||
# 1.1.1 (22-02-2022)
|
||||
|
||||
- Ignore HTTP::Server::Response patching for crystal >= 1.3.0 [#628](https://github.com/kemalcr/kemal/pull/628). Thanks @SamantazFox :pray:
|
||||
# 1.1.0 (02-09-2021)
|
||||
|
||||
- You can now set your own application name for startup message [#606](https://github.com/kemalcr/kemal/pull/606). Thanks @aravindavk :pray:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
name: kemal
|
||||
version: 1.2.0
|
||||
version: 1.1.0
|
||||
|
||||
authors:
|
||||
- Serdar Dogruyol <dogruyolserdar@gmail.com>
|
||||
|
@ -15,7 +15,7 @@ dependencies:
|
|||
development_dependencies:
|
||||
ameba:
|
||||
github: crystal-ameba/ameba
|
||||
version: ~> 1.0
|
||||
version: ~> 0.14.0
|
||||
|
||||
crystal: ">= 0.36.0"
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
Hello <%= name %>
|
||||
|
||||
<% content_for "meta" do %>
|
||||
<title>Kemal Spec</title>
|
||||
<% content_for "custom" do %>
|
||||
<h1>Hello from otherside</h1>
|
||||
<% end %>
|
|
@ -1,8 +1,6 @@
|
|||
<html>
|
||||
<head>
|
||||
<%= yield_content "meta" %>
|
||||
</head>
|
||||
<body>
|
||||
<%= content %>
|
||||
<%= yield_content "custom" %>
|
||||
</body>
|
||||
</html>
|
|
@ -1,9 +1,7 @@
|
|||
<html>
|
||||
<head>
|
||||
<%= yield_content "meta" %>
|
||||
</head>
|
||||
<body>
|
||||
<%= content %>
|
||||
<%= yield_content "custom" %>
|
||||
<%= var1 %>
|
||||
<%= var2 %>
|
||||
</body>
|
||||
|
|
|
@ -143,40 +143,4 @@ describe "Kemal::RouteHandler" do
|
|||
client_response.body.should eq("Redirecting to /login")
|
||||
client_response.headers.has_key?("Location").should eq(true)
|
||||
end
|
||||
|
||||
it "redirects and closes response in before filter" do
|
||||
filter_handler = Kemal::FilterHandler.new
|
||||
filter_handler._add_route_filter("GET", "/", :before) do |env|
|
||||
env.redirect "/login"
|
||||
end
|
||||
Kemal.config.add_filter_handler(filter_handler)
|
||||
|
||||
get "/" do
|
||||
"home page"
|
||||
end
|
||||
|
||||
request = HTTP::Request.new("GET", "/")
|
||||
client_response = call_request_on_app(request)
|
||||
client_response.status_code.should eq(302)
|
||||
client_response.body.should eq("")
|
||||
client_response.headers.has_key?("Location").should eq(true)
|
||||
end
|
||||
|
||||
it "redirects in before filter without closing response" do
|
||||
filter_handler = Kemal::FilterHandler.new
|
||||
filter_handler._add_route_filter("GET", "/", :before) do |env|
|
||||
env.redirect "/login", close: false
|
||||
end
|
||||
Kemal.config.add_filter_handler(filter_handler)
|
||||
|
||||
get "/" do
|
||||
"home page"
|
||||
end
|
||||
|
||||
request = HTTP::Request.new("GET", "/")
|
||||
client_response = call_request_on_app(request)
|
||||
client_response.status_code.should eq(302)
|
||||
client_response.body.should eq("home page")
|
||||
client_response.headers.has_key?("Location").should eq(true)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -4,8 +4,8 @@ require "../src/*"
|
|||
include Kemal
|
||||
|
||||
class CustomLogHandler < Kemal::BaseLogHandler
|
||||
def call(context)
|
||||
call_next(context)
|
||||
def call(env)
|
||||
call_next env
|
||||
end
|
||||
|
||||
def write(message)
|
||||
|
|
|
@ -23,15 +23,6 @@ describe Kemal::StaticFileHandler do
|
|||
response.body.should eq(File.read("#{__DIR__}/static/dir/test.txt"))
|
||||
end
|
||||
|
||||
it "should serve the 'index.html' file when a directory is requested and index serving is enabled" do
|
||||
serve_static({"dir_index" => true})
|
||||
response = handle HTTP::Request.new("GET", "/dir/")
|
||||
response.status_code.should eq(200)
|
||||
response.headers["Content-Type"].should eq "text/html"
|
||||
response.headers["Etag"].should contain "W/\""
|
||||
response.body.should eq(File.read("#{__DIR__}/static/dir/index.html"))
|
||||
end
|
||||
|
||||
it "should respond with 304 if file has not changed" do
|
||||
response = handle HTTP::Request.new("GET", "/dir/test.txt")
|
||||
response.status_code.should eq(200)
|
||||
|
|
|
@ -22,7 +22,7 @@ describe "Views" do
|
|||
end
|
||||
request = HTTP::Request.new("GET", "/view/world")
|
||||
client_response = call_request_on_app(request)
|
||||
client_response.body.strip.should eq("<html>Hello world\n</html>")
|
||||
client_response.body.should contain("Hello world")
|
||||
end
|
||||
|
||||
it "renders layout" do
|
||||
|
@ -56,17 +56,7 @@ describe "Views" do
|
|||
end
|
||||
request = HTTP::Request.new("GET", "/view/world")
|
||||
client_response = call_request_on_app(request)
|
||||
client_response.body.scan("Hello world").size.should eq(1)
|
||||
client_response.body.should contain("<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>")
|
||||
client_response.body.should contain("Hello world")
|
||||
client_response.body.should contain("<h1>Hello from otherside</h1>")
|
||||
end
|
||||
end
|
||||
|
|
14
src/kemal.cr
14
src/kemal.cr
|
@ -7,18 +7,18 @@ require "./kemal/helpers/*"
|
|||
|
||||
module Kemal
|
||||
# Overload of `self.run` with the default startup logging.
|
||||
def self.run(port : Int32?, args = ARGV, trap_signal : Bool = true)
|
||||
self.run(port, args, trap_signal) { }
|
||||
def self.run(port : Int32?, args = ARGV)
|
||||
self.run(port, args) { }
|
||||
end
|
||||
|
||||
# Overload of `self.run` without port.
|
||||
def self.run(args = ARGV, trap_signal : Bool = true)
|
||||
self.run(nil, args: args, trap_signal: trap_signal)
|
||||
def self.run(args = ARGV)
|
||||
self.run(nil, args: args)
|
||||
end
|
||||
|
||||
# Overload of `self.run` to allow just a block.
|
||||
def self.run(args = ARGV, &block)
|
||||
self.run(nil, args: args, trap_signal: true, &block)
|
||||
self.run(nil, args: args, &block)
|
||||
end
|
||||
|
||||
# The command to run a `Kemal` application.
|
||||
|
@ -27,7 +27,7 @@ module Kemal
|
|||
#
|
||||
# To use custom command line arguments, set args to nil
|
||||
#
|
||||
def self.run(port : Int32? = nil, args = ARGV, trap_signal : Bool = true, &block)
|
||||
def self.run(port : Int32? = nil, args = ARGV, &block)
|
||||
Kemal::CLI.new args
|
||||
config = Kemal.config
|
||||
config.setup
|
||||
|
@ -36,7 +36,7 @@ module Kemal
|
|||
# Test environment doesn't need to have signal trap and logging.
|
||||
if config.env != "test"
|
||||
setup_404
|
||||
setup_trap_signal if trap_signal
|
||||
setup_trap_signal
|
||||
end
|
||||
|
||||
server = config.server ||= HTTP::Server.new(config.handlers)
|
||||
|
|
|
@ -31,7 +31,7 @@ module Kemal
|
|||
@host_binding = "0.0.0.0"
|
||||
@port = 3000
|
||||
@env = ENV["KEMAL_ENV"]? || "development"
|
||||
@serve_static = {"dir_listing" => false, "gzip" => true, "dir_index" => false}
|
||||
@serve_static = {"dir_listing" => false, "gzip" => true}
|
||||
@public_folder = "./public"
|
||||
@logging = true
|
||||
@logger = nil
|
||||
|
|
|
@ -17,11 +17,10 @@ class HTTP::Server
|
|||
@params ||= Kemal::ParamParser.new(@request, route_lookup.params)
|
||||
end
|
||||
|
||||
def redirect(url : String, status_code : Int32 = 302, *, body : String? = nil, close : Bool = true)
|
||||
def redirect(url : String, status_code : Int32 = 302, *, body : String? = nil)
|
||||
@response.headers.add "Location", url
|
||||
@response.status_code = status_code
|
||||
@response.print(body) if body
|
||||
@response.close if close
|
||||
end
|
||||
|
||||
def route
|
||||
|
|
|
@ -1,7 +1,3 @@
|
|||
# This override collides with the new stdlib of Crystal 1.3
|
||||
# See https://github.com/kemalcr/kemal/issues/627 for more details
|
||||
{{ skip_file if compare_versions(Crystal::VERSION, "1.3.0") >= 0 }}
|
||||
|
||||
class HTTP::Server::Response
|
||||
class Output
|
||||
def close
|
||||
|
|
|
@ -27,8 +27,8 @@ module Kemal
|
|||
end
|
||||
end
|
||||
|
||||
def call(context : HTTP::Server::Context)
|
||||
call_next(context)
|
||||
def call(env : HTTP::Server::Context)
|
||||
call_next(env)
|
||||
end
|
||||
|
||||
# Processes the path based on `only` paths which is a `Array(String)`.
|
||||
|
|
|
@ -216,7 +216,20 @@ private def multipart(file, env : HTTP::Server::Context)
|
|||
env.response.headers["Accept-Ranges"] = "bytes"
|
||||
env.response.headers["Content-Range"] = "bytes #{startb}-#{endb}/#{fileb}" # MUST
|
||||
|
||||
file.seek(startb)
|
||||
if startb > 1024
|
||||
skipped = 0_i64
|
||||
# file.skip only accepts values less or equal to 1024 (buffer size, undocumented)
|
||||
until (increase_skipped = skipped + 1024_i64) > startb
|
||||
file.skip(1024)
|
||||
skipped = increase_skipped
|
||||
end
|
||||
if (skipped_minus_startb = skipped - startb) > 0
|
||||
file.skip skipped_minus_startb
|
||||
end
|
||||
else
|
||||
file.skip(startb)
|
||||
end
|
||||
|
||||
IO.copy(file, env.response, content_length)
|
||||
else
|
||||
env.response.content_length = fileb
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
CONTENT_FOR_BLOCKS = Hash(String, Tuple(String, Proc(Nil))).new
|
||||
CONTENT_FOR_BLOCKS = Hash(String, Tuple(String, Proc(String))).new
|
||||
|
||||
# `content_for` is a set of helpers that allows you to capture
|
||||
# blocks inside views to be rendered later during the request. The most
|
||||
|
@ -34,7 +34,13 @@ CONTENT_FOR_BLOCKS = Hash(String, Tuple(String, Proc(Nil))).new
|
|||
# layout, inside the <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__)
|
||||
CONTENT_FOR_BLOCKS[{{key}}] = Tuple.new {{file}}, ->() { {{ yield }} }
|
||||
%proc = ->() {
|
||||
__view_io__ = IO::Memory.new
|
||||
{{ yield }}
|
||||
__view_io__.to_s
|
||||
}
|
||||
|
||||
CONTENT_FOR_BLOCKS[{{key}}] = Tuple.new {{file}}, %proc
|
||||
nil
|
||||
end
|
||||
|
||||
|
@ -43,14 +49,7 @@ macro yield_content(key)
|
|||
if CONTENT_FOR_BLOCKS.has_key?({{key}})
|
||||
__caller_filename__ = CONTENT_FOR_BLOCKS[{{key}}][0]
|
||||
%proc = CONTENT_FOR_BLOCKS[{{key}}][1]
|
||||
|
||||
if __content_filename__ == __caller_filename__
|
||||
%old_content_io, content_io = content_io, IO::Memory.new
|
||||
%proc.call
|
||||
%result = content_io.to_s
|
||||
content_io = %old_content_io
|
||||
%result
|
||||
end
|
||||
%proc.call if __content_filename__ == __caller_filename__
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -61,12 +60,10 @@ end
|
|||
# ```
|
||||
macro render(filename, layout)
|
||||
__content_filename__ = {{filename}}
|
||||
content_io = IO::Memory.new
|
||||
ECR.embed {{filename}}, content_io
|
||||
content = content_io.to_s
|
||||
layout_io = IO::Memory.new
|
||||
ECR.embed {{layout}}, layout_io
|
||||
layout_io.to_s
|
||||
io = IO::Memory.new
|
||||
content = ECR.embed {{filename}}, io
|
||||
ECR.embed {{layout}}, io
|
||||
io.to_s
|
||||
end
|
||||
|
||||
# Render view with the given filename.
|
||||
|
|
|
@ -36,7 +36,7 @@ module Kemal
|
|||
end
|
||||
|
||||
file_path = File.join(@public_dir, expanded_path)
|
||||
is_dir = Dir.exists?(file_path)
|
||||
is_dir = Dir.exists? file_path
|
||||
|
||||
if request_path != expanded_path
|
||||
redirect_to context, expanded_path
|
||||
|
@ -44,19 +44,8 @@ module Kemal
|
|||
redirect_to context, expanded_path + '/'
|
||||
end
|
||||
|
||||
if is_dir
|
||||
if config.is_a?(Hash) && config.fetch("dir_index", false) && File.exists?(File.join(file_path, "index.html"))
|
||||
file_path = File.join(@public_dir, expanded_path, "index.html")
|
||||
|
||||
last_modified = modification_time(file_path)
|
||||
add_cache_headers(context.response.headers, last_modified)
|
||||
|
||||
if cache_request?(context, last_modified)
|
||||
context.response.status_code = 304
|
||||
return
|
||||
end
|
||||
send_file(context, file_path)
|
||||
elsif config.is_a?(Hash) && config.fetch("dir_listing", false)
|
||||
if Dir.exists?(file_path)
|
||||
if config.is_a?(Hash) && config["dir_listing"] == true
|
||||
context.response.content_type = "text/html"
|
||||
directory_listing(context.response, request_path, file_path)
|
||||
else
|
||||
|
@ -75,9 +64,5 @@ module Kemal
|
|||
call_next(context)
|
||||
end
|
||||
end
|
||||
|
||||
private def modification_time(file_path)
|
||||
File.info(file_path).modification_time
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue