Merge branch 'master' into master
This commit is contained in:
commit
7684aeaf33
15 changed files with 75 additions and 194 deletions
33
CHANGELOG.md
33
CHANGELOG.md
|
@ -1,23 +1,34 @@
|
|||
# Next
|
||||
|
||||
- `env.params.files` is now an `Array(FileUpload)`. You can iterate over to access the images.
|
||||
- *[breaking change]* Removed `env.params.files`. You can use Crystal's built-in `HTTP::FormData.parse` instead
|
||||
|
||||
```ruby
|
||||
env.params.files.each do |file|
|
||||
post "/upload" do |env|
|
||||
HTTP::FormData.parse(env.request) do |upload|
|
||||
filename = file.filename
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
- *[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
|
||||
```
|
||||
|
||||
- *[breaking change]* Removed `_method` magic param
|
||||
|
||||
# 0.23.0 (17-06-2018)
|
||||
|
||||
- Crystal 0.25.0 support 🎉
|
||||
|
|
|
@ -13,8 +13,8 @@ describe "Kemal::FilterHandler" do
|
|||
|
||||
test_filter.modified.should eq("false")
|
||||
request = HTTP::Request.new("GET", "/greetings")
|
||||
create_request_and_return_io(filter_middleware, request)
|
||||
io_with_context = create_request_and_return_io(kemal, request)
|
||||
create_request_and_return_io_and_context(filter_middleware, request)
|
||||
io_with_context = create_request_and_return_io_and_context(kemal, request)[0]
|
||||
client_response = HTTP::Client::Response.from_io(io_with_context, decompress: false)
|
||||
client_response.body.should eq("true")
|
||||
end
|
||||
|
@ -33,14 +33,14 @@ describe "Kemal::FilterHandler" do
|
|||
test_filter.modified.should eq("false")
|
||||
|
||||
request = HTTP::Request.new("GET", "/greetings")
|
||||
create_request_and_return_io(filter_middleware, request)
|
||||
io_with_context = create_request_and_return_io(kemal, request)
|
||||
create_request_and_return_io_and_context(filter_middleware, request)
|
||||
io_with_context = create_request_and_return_io_and_context(kemal, request)[0]
|
||||
client_response = HTTP::Client::Response.from_io(io_with_context, decompress: false)
|
||||
client_response.body.should eq("true")
|
||||
|
||||
request = HTTP::Request.new("POST", "/greetings")
|
||||
create_request_and_return_io(filter_middleware, request)
|
||||
io_with_context = create_request_and_return_io(kemal, request)
|
||||
create_request_and_return_io_and_context(filter_middleware, request)
|
||||
io_with_context = create_request_and_return_io_and_context(kemal, request)[0]
|
||||
client_response = HTTP::Client::Response.from_io(io_with_context, decompress: false)
|
||||
client_response.body.should eq("true")
|
||||
end
|
||||
|
@ -61,14 +61,14 @@ describe "Kemal::FilterHandler" do
|
|||
test_filter.modified.should eq("false")
|
||||
|
||||
request = HTTP::Request.new("GET", "/greetings")
|
||||
create_request_and_return_io(filter_middleware, request)
|
||||
io_with_context = create_request_and_return_io(kemal, request)
|
||||
create_request_and_return_io_and_context(filter_middleware, request)
|
||||
io_with_context = create_request_and_return_io_and_context(kemal, request)[0]
|
||||
client_response = HTTP::Client::Response.from_io(io_with_context, decompress: false)
|
||||
client_response.body.should eq("false")
|
||||
|
||||
request = HTTP::Request.new("POST", "/greetings")
|
||||
create_request_and_return_io(filter_middleware, request)
|
||||
io_with_context = create_request_and_return_io(kemal, request)
|
||||
create_request_and_return_io_and_context(filter_middleware, request)
|
||||
io_with_context = create_request_and_return_io_and_context(kemal, request)[0]
|
||||
client_response = HTTP::Client::Response.from_io(io_with_context, decompress: false)
|
||||
client_response.body.should eq("false")
|
||||
end
|
||||
|
@ -85,8 +85,8 @@ describe "Kemal::FilterHandler" do
|
|||
|
||||
test_filter.modified.should eq("false")
|
||||
request = HTTP::Request.new("GET", "/greetings")
|
||||
create_request_and_return_io(filter_middleware, request)
|
||||
io_with_context = create_request_and_return_io(kemal, request)
|
||||
create_request_and_return_io_and_context(filter_middleware, request)
|
||||
io_with_context = create_request_and_return_io_and_context(kemal, request)[0]
|
||||
client_response = HTTP::Client::Response.from_io(io_with_context, decompress: false)
|
||||
client_response.body.should eq("true")
|
||||
end
|
||||
|
@ -105,14 +105,14 @@ describe "Kemal::FilterHandler" do
|
|||
test_filter.modified.should eq("false")
|
||||
|
||||
request = HTTP::Request.new("GET", "/greetings")
|
||||
create_request_and_return_io(filter_middleware, request)
|
||||
io_with_context = create_request_and_return_io(kemal, request)
|
||||
create_request_and_return_io_and_context(filter_middleware, request)
|
||||
io_with_context = create_request_and_return_io_and_context(kemal, request)[0]
|
||||
client_response = HTTP::Client::Response.from_io(io_with_context, decompress: false)
|
||||
client_response.body.should eq("true")
|
||||
|
||||
request = HTTP::Request.new("POST", "/greetings")
|
||||
create_request_and_return_io(filter_middleware, request)
|
||||
io_with_context = create_request_and_return_io(kemal, request)
|
||||
create_request_and_return_io_and_context(filter_middleware, request)
|
||||
io_with_context = create_request_and_return_io_and_context(kemal, request)[0]
|
||||
client_response = HTTP::Client::Response.from_io(io_with_context, decompress: false)
|
||||
client_response.body.should eq("true")
|
||||
end
|
||||
|
@ -132,14 +132,14 @@ describe "Kemal::FilterHandler" do
|
|||
|
||||
test_filter.modified.should eq("false")
|
||||
request = HTTP::Request.new("GET", "/greetings")
|
||||
create_request_and_return_io(filter_middleware, request)
|
||||
io_with_context = create_request_and_return_io(kemal, request)
|
||||
create_request_and_return_io_and_context(filter_middleware, request)
|
||||
io_with_context = create_request_and_return_io_and_context(kemal, request)[0]
|
||||
client_response = HTTP::Client::Response.from_io(io_with_context, decompress: false)
|
||||
client_response.body.should eq("false")
|
||||
|
||||
request = HTTP::Request.new("POST", "/greetings")
|
||||
create_request_and_return_io(filter_middleware, request)
|
||||
io_with_context = create_request_and_return_io(kemal, request)
|
||||
create_request_and_return_io_and_context(filter_middleware, request)
|
||||
io_with_context = create_request_and_return_io_and_context(kemal, request)[0]
|
||||
client_response = HTTP::Client::Response.from_io(io_with_context, decompress: false)
|
||||
client_response.body.should eq("false")
|
||||
end
|
||||
|
@ -166,20 +166,20 @@ describe "Kemal::FilterHandler" do
|
|||
test_filter_second.modified.should eq("false")
|
||||
test_filter_third.modified.should eq("false")
|
||||
request = HTTP::Request.new("GET", "/greetings")
|
||||
create_request_and_return_io(filter_middleware, request)
|
||||
io_with_context = create_request_and_return_io(kemal, request)
|
||||
create_request_and_return_io_and_context(filter_middleware, request)
|
||||
io_with_context = create_request_and_return_io_and_context(kemal, request)[0]
|
||||
client_response = HTTP::Client::Response.from_io(io_with_context, decompress: false)
|
||||
client_response.body.should eq("true")
|
||||
|
||||
request = HTTP::Request.new("POST", "/greetings")
|
||||
create_request_and_return_io(filter_middleware, request)
|
||||
io_with_context = create_request_and_return_io(kemal, request)
|
||||
create_request_and_return_io_and_context(filter_middleware, request)
|
||||
io_with_context = create_request_and_return_io_and_context(kemal, request)[0]
|
||||
client_response = HTTP::Client::Response.from_io(io_with_context, decompress: false)
|
||||
client_response.body.should eq("false")
|
||||
|
||||
request = HTTP::Request.new("PUT", "/greetings")
|
||||
create_request_and_return_io(filter_middleware, request)
|
||||
io_with_context = create_request_and_return_io(kemal, request)
|
||||
create_request_and_return_io_and_context(filter_middleware, request)
|
||||
io_with_context = create_request_and_return_io_and_context(kemal, request)[0]
|
||||
client_response = HTTP::Client::Response.from_io(io_with_context, decompress: false)
|
||||
client_response.body.should eq("true")
|
||||
end
|
||||
|
|
|
@ -28,8 +28,8 @@ describe "ParamParser" do
|
|||
end
|
||||
request = HTTP::Request.new("POST", "/hello/cemal")
|
||||
# Radix tree MUST be run to parse url params.
|
||||
create_request_and_return_io(kemal, request)
|
||||
url_params = Kemal::ParamParser.new(request).url
|
||||
context = create_request_and_return_io_and_context(kemal, request)[1]
|
||||
url_params = Kemal::ParamParser.new(request, context.route_lookup.params).url
|
||||
url_params["hasan"].should eq "cemal"
|
||||
end
|
||||
|
||||
|
@ -43,8 +43,8 @@ describe "ParamParser" do
|
|||
end
|
||||
request = HTTP::Request.new("POST", "/hello/sam%2Bspec%40gmail.com/%2419.99/a%C3%B1o")
|
||||
# Radix tree MUST be run to parse url params.
|
||||
create_request_and_return_io(kemal, request)
|
||||
url_params = Kemal::ParamParser.new(request).url
|
||||
context = create_request_and_return_io_and_context(kemal, request)[1]
|
||||
url_params = Kemal::ParamParser.new(request, context.route_lookup.params).url
|
||||
url_params["email"].should eq "sam+spec@gmail.com"
|
||||
url_params["money"].should eq "$19.99"
|
||||
url_params["spanish"].should eq "año"
|
||||
|
|
|
@ -102,48 +102,6 @@ describe "Kemal::RouteHandler" do
|
|||
client_response.body.should eq("Skills ruby,crystal")
|
||||
end
|
||||
|
||||
it "checks for _method param in POST request to simulate PUT" do
|
||||
put "/" do
|
||||
"Hello World from PUT"
|
||||
end
|
||||
request = HTTP::Request.new(
|
||||
"POST",
|
||||
"/",
|
||||
body: "_method=PUT",
|
||||
headers: HTTP::Headers{"Content-Type" => "application/x-www-form-urlencoded"}
|
||||
)
|
||||
client_response = call_request_on_app(request)
|
||||
client_response.body.should eq("Hello World from PUT")
|
||||
end
|
||||
|
||||
it "checks for _method param in POST request to simulate PATCH" do
|
||||
patch "/" do
|
||||
"Hello World from PATCH"
|
||||
end
|
||||
request = HTTP::Request.new(
|
||||
"POST",
|
||||
"/",
|
||||
body: "_method=PATCH",
|
||||
headers: HTTP::Headers{"Content-Type" => "application/x-www-form-urlencoded; charset=UTF-8"}
|
||||
)
|
||||
client_response = call_request_on_app(request)
|
||||
client_response.body.should eq("Hello World from PATCH")
|
||||
end
|
||||
|
||||
it "checks for _method param in POST request to simulate DELETE" do
|
||||
delete "/" do
|
||||
"Hello World from DELETE"
|
||||
end
|
||||
request = HTTP::Request.new(
|
||||
"POST",
|
||||
"/",
|
||||
body: "_method=DELETE",
|
||||
headers: HTTP::Headers{"Content-Type" => "application/x-www-form-urlencoded; charset=UTF-8"}
|
||||
)
|
||||
client_response = call_request_on_app(request)
|
||||
client_response.body.should eq("Hello World from DELETE")
|
||||
end
|
||||
|
||||
it "can process HTTP HEAD requests for defined GET routes" do
|
||||
get "/" do
|
||||
"Hello World from GET"
|
||||
|
|
|
@ -29,17 +29,17 @@ end
|
|||
add_context_storage_type(TestContextStorageType)
|
||||
add_context_storage_type(AnotherContextStorageType)
|
||||
|
||||
def create_request_and_return_io(handler, request)
|
||||
def create_request_and_return_io_and_context(handler, request)
|
||||
io = IO::Memory.new
|
||||
response = HTTP::Server::Response.new(io)
|
||||
context = HTTP::Server::Context.new(request, response)
|
||||
handler.call(context)
|
||||
response.close
|
||||
io.rewind
|
||||
io
|
||||
{io, context}
|
||||
end
|
||||
|
||||
def create_ws_request_and_return_io(handler, request)
|
||||
def create_ws_request_and_return_io_and_context(handler, request)
|
||||
io = IO::Memory.new
|
||||
response = HTTP::Server::Response.new(io)
|
||||
context = HTTP::Server::Context.new(request, response)
|
||||
|
@ -49,6 +49,7 @@ def create_ws_request_and_return_io(handler, request)
|
|||
# Raises because the IO::Memory is empty
|
||||
end
|
||||
io.rewind
|
||||
{io, context}
|
||||
end
|
||||
|
||||
def call_request_on_app(request)
|
||||
|
|
|
@ -30,6 +30,7 @@ describe Kemal::StaticFileHandler do
|
|||
|
||||
headers = HTTP::Headers{"If-None-Match" => etag}
|
||||
response = handle HTTP::Request.new("GET", "/dir/test.txt", headers)
|
||||
response.headers["Content-Type"]?.should be_nil
|
||||
response.status_code.should eq(304)
|
||||
response.body.should eq ""
|
||||
end
|
||||
|
|
|
@ -32,13 +32,13 @@ describe "Kemal::WebSocketHandler" do
|
|||
}
|
||||
request = HTTP::Request.new("GET", "/", headers)
|
||||
|
||||
io_with_context = create_ws_request_and_return_io(handler, request)
|
||||
io_with_context = create_ws_request_and_return_io_and_context(handler, request)[0]
|
||||
io_with_context.to_s.should eq("HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n\r\n\x81\u0005Match")
|
||||
end
|
||||
|
||||
it "fetches named url parameters" do
|
||||
handler = Kemal::WebSocketHandler::INSTANCE
|
||||
ws "/:id" { |_, c| c.params.url["id"] }
|
||||
ws "/:id" { |_, c| c.ws_route_lookup.params["id"] }
|
||||
headers = HTTP::Headers{
|
||||
"Upgrade" => "websocket",
|
||||
"Connection" => "Upgrade",
|
||||
|
@ -46,7 +46,7 @@ describe "Kemal::WebSocketHandler" do
|
|||
"Sec-WebSocket-Version" => "13",
|
||||
}
|
||||
request = HTTP::Request.new("GET", "/1234", headers)
|
||||
io_with_context = create_ws_request_and_return_io(handler, request)
|
||||
io_with_context = create_ws_request_and_return_io_and_context(handler, request)[0]
|
||||
io_with_context.to_s.should eq("HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n\r\n")
|
||||
end
|
||||
|
||||
|
|
|
@ -14,12 +14,7 @@ class HTTP::Server
|
|||
end
|
||||
|
||||
def params
|
||||
@request.url_params ||= route_lookup.params
|
||||
@params ||= if @request.param_parser
|
||||
@request.param_parser.not_nil!
|
||||
else
|
||||
Kemal::ParamParser.new(@request)
|
||||
end
|
||||
@params ||= Kemal::ParamParser.new(@request, route_lookup.params)
|
||||
end
|
||||
|
||||
def redirect(url : String, status_code : Int32 = 302)
|
||||
|
@ -36,7 +31,7 @@ class HTTP::Server
|
|||
end
|
||||
|
||||
def route_lookup
|
||||
Kemal::RouteHandler::INSTANCE.lookup_route(@request.override_method.as(String), @request.path)
|
||||
Kemal::RouteHandler::INSTANCE.lookup_route(@request.method.as(String), @request.path)
|
||||
end
|
||||
|
||||
def route_defined?
|
||||
|
|
|
@ -1,32 +0,0 @@
|
|||
class HTTP::Request
|
||||
property override_method
|
||||
property url_params : Hash(String, String)?
|
||||
getter param_parser : Kemal::ParamParser?
|
||||
|
||||
def override_method
|
||||
@override_method ||= check_for_method_override!
|
||||
end
|
||||
|
||||
def content_type
|
||||
@headers["Content-Type"]?
|
||||
end
|
||||
|
||||
# Checks if method contained in _method param is valid one
|
||||
def self.override_method_valid?(override_method : String)
|
||||
override_method = override_method.upcase
|
||||
override_method == "PUT" || override_method == "PATCH" || override_method == "DELETE"
|
||||
end
|
||||
|
||||
# Checks if request params contain _method param to override request incoming method
|
||||
private def check_for_method_override!
|
||||
@override_method = @method
|
||||
if @method == "POST"
|
||||
@param_parser = Kemal::ParamParser.new(self)
|
||||
params = @param_parser.not_nil!.body
|
||||
if params.has_key?("_method") && HTTP::Request.override_method_valid?(params["_method"])
|
||||
@override_method = params["_method"]
|
||||
end
|
||||
end
|
||||
@override_method
|
||||
end
|
||||
end
|
|
@ -1,25 +0,0 @@
|
|||
module Kemal
|
||||
# :nodoc:
|
||||
struct FileUpload
|
||||
getter tmpfile : Tempfile
|
||||
getter filename : String?
|
||||
getter headers : HTTP::Headers
|
||||
getter creation_time : Time?
|
||||
getter modification_time : Time?
|
||||
getter read_time : Time?
|
||||
getter size : UInt64?
|
||||
|
||||
def initialize(upload)
|
||||
@tmpfile = Tempfile.new(filename)
|
||||
::File.open(@tmpfile.path, "w") do |file|
|
||||
IO.copy(upload.body, file)
|
||||
end
|
||||
@filename = upload.filename
|
||||
@headers = upload.headers
|
||||
@creation_time = upload.creation_time
|
||||
@modification_time = upload.modification_time
|
||||
@read_time = upload.read_time
|
||||
@size = upload.size
|
||||
end
|
||||
end
|
||||
end
|
|
@ -14,12 +14,12 @@ module Kemal
|
|||
def call(context : HTTP::Server::Context)
|
||||
return call_next(context) unless context.route_defined?
|
||||
call_block_for_path_type("ALL", context.request.path, :before, context)
|
||||
call_block_for_path_type(context.request.override_method, context.request.path, :before, context)
|
||||
call_block_for_path_type(context.request.method, context.request.path, :before, context)
|
||||
if Kemal.config.error_handlers.has_key?(context.response.status_code)
|
||||
raise Kemal::Exceptions::CustomException.new(context)
|
||||
end
|
||||
call_next(context)
|
||||
call_block_for_path_type(context.request.override_method, context.request.path, :after, context)
|
||||
call_block_for_path_type(context.request.method, context.request.path, :after, context)
|
||||
call_block_for_path_type("ALL", context.request.path, :after, context)
|
||||
context
|
||||
end
|
||||
|
|
|
@ -8,7 +8,7 @@ module Kemal::Exceptions
|
|||
|
||||
class RouteNotFound < Exception
|
||||
def initialize(context : HTTP::Server::Context)
|
||||
super "Requested path: '#{context.request.override_method}:#{context.request.path}' was not found."
|
||||
super "Requested path: '#{context.request.method}:#{context.request.path}' was not found."
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -9,14 +9,11 @@ module Kemal
|
|||
PARTS = %w(url query body json)
|
||||
# :nodoc:
|
||||
alias AllParamTypes = Nil | String | Int64 | Float64 | Bool | Hash(String, JSON::Any) | Array(JSON::Any)
|
||||
getter files
|
||||
|
||||
def initialize(@request : HTTP::Request)
|
||||
@url = {} of String => String
|
||||
def initialize(@request : HTTP::Request, @url : Hash(String, String) = {} of String => String)
|
||||
@query = HTTP::Params.new({} of String => Array(String))
|
||||
@body = HTTP::Params.new({} of String => Array(String))
|
||||
@json = {} of String => AllParamTypes
|
||||
@files = [] of FileUpload
|
||||
@url_parsed = false
|
||||
@query_parsed = false
|
||||
@body_parsed = false
|
||||
|
@ -42,16 +39,12 @@ module Kemal
|
|||
{% end %}
|
||||
|
||||
private def parse_body
|
||||
content_type = @request.content_type
|
||||
content_type = @request.headers["Content-Type"]?
|
||||
return unless content_type
|
||||
if content_type.try(&.starts_with?(URL_ENCODED_FORM))
|
||||
@body = parse_part(@request.body)
|
||||
return
|
||||
end
|
||||
if content_type.try(&.starts_with?(MULTIPART_FORM))
|
||||
parse_file_upload
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
private def parse_query
|
||||
|
@ -59,23 +52,7 @@ module Kemal
|
|||
end
|
||||
|
||||
private def parse_url
|
||||
if params = @request.url_params
|
||||
params.each do |key, value|
|
||||
@url[key] = unescape_url_param(value)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private def parse_file_upload
|
||||
HTTP::FormData.parse(@request) do |upload|
|
||||
next unless upload
|
||||
filename = upload.filename
|
||||
if !filename.nil?
|
||||
@files << FileUpload.new(upload: upload)
|
||||
else
|
||||
@body.add(upload.name, upload.body.gets_to_end)
|
||||
end
|
||||
end
|
||||
@url.each { |key, value| @url[key] = unescape_url_param(value) }
|
||||
end
|
||||
|
||||
# Parses JSON request body if Content-Type is `application/json`.
|
||||
|
|
|
@ -50,21 +50,17 @@ module Kemal
|
|||
return call_next(context)
|
||||
end
|
||||
elsif File.exists?(file_path)
|
||||
return if etag(context, file_path)
|
||||
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)
|
||||
else
|
||||
call_next(context)
|
||||
end
|
||||
end
|
||||
|
||||
private def etag(context : HTTP::Server::Context, file_path : String)
|
||||
etag = %{W/"#{File.info(file_path).modification_time.epoch.to_s}"}
|
||||
context.response.headers["ETag"] = etag
|
||||
return false if !context.request.headers["If-None-Match"]? || context.request.headers["If-None-Match"] != etag
|
||||
context.response.headers.delete "Content-Type"
|
||||
context.response.content_length = 0
|
||||
context.response.status_code = 304 # not modified
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -11,7 +11,6 @@ module Kemal
|
|||
|
||||
def call(context : HTTP::Server::Context)
|
||||
return call_next(context) unless context.ws_route_defined? && websocket_upgrade_request?(context)
|
||||
context.request.url_params ||= context.ws_route_lookup.params
|
||||
content = context.websocket.call(context)
|
||||
context.response.print(content)
|
||||
context
|
||||
|
|
Loading…
Reference in a new issue