Merge pull request #123 from f/master

Upgrade ParamParser to make it more convinient.
This commit is contained in:
Serdar Dogruyol 2016-03-07 13:41:24 +02:00
commit a194bebe31
7 changed files with 92 additions and 56 deletions

View file

@ -3,19 +3,31 @@ require "./spec_helper"
describe "ParamParser" do describe "ParamParser" do
it "parses query params" do it "parses query params" do
route = Route.new "POST", "/" do |env| route = Route.new "POST", "/" do |env|
hasan = env.params["hasan"] hasan = env.params.query["hasan"]
"Hello #{hasan}" "Hello #{hasan}"
end end
request = HTTP::Request.new("POST", "/?hasan=cemal") request = HTTP::Request.new("POST", "/?hasan=cemal")
params = Kemal::ParamParser.new(request).parse query_params = Kemal::ParamParser.new(request).query
params["hasan"].should eq "cemal" query_params["hasan"].should eq "cemal"
end
it "parses url params" do
kemal = Kemal::RouteHandler::INSTANCE
kemal.add_route "POST", "/hello/:hasan" do |env|
"hello #{env.params.url["hasan"]}"
end
request = HTTP::Request.new("POST", "/hello/cemal")
# Radix tree MUST be run to parse url params.
io_with_context = create_request_and_return_io(kemal, request)
url_params = Kemal::ParamParser.new(request).url
url_params["hasan"].should eq "cemal"
end end
it "parses request body" do it "parses request body" do
route = Route.new "POST", "/" do |env| route = Route.new "POST", "/" do |env|
name = env.params["name"] name = env.params.query["name"]
age = env.params["age"] age = env.params.query["age"]
hasan = env.params["hasan"] hasan = env.params.body["hasan"]
"Hello #{name} #{hasan} #{age}" "Hello #{name} #{hasan} #{age}"
end end
@ -26,8 +38,11 @@ describe "ParamParser" do
headers: HTTP::Headers{"Content-Type": "application/x-www-form-urlencoded"}, headers: HTTP::Headers{"Content-Type": "application/x-www-form-urlencoded"},
) )
params = Kemal::ParamParser.new(request).parse query_params = Kemal::ParamParser.new(request).query
params.should eq({"hasan" => "cemal", "name" => "serdar", "age" => "99"}) query_params.should eq({"hasan" => "cemal"})
body_params = Kemal::ParamParser.new(request).body
body_params.should eq({"name" => "serdar", "age" => "99"})
end end
context "when content type is application/json" do context "when content type is application/json" do
@ -41,8 +56,8 @@ describe "ParamParser" do
headers: HTTP::Headers{"Content-Type": "application/json"}, headers: HTTP::Headers{"Content-Type": "application/json"},
) )
params = Kemal::ParamParser.new(request).parse json_params = Kemal::ParamParser.new(request).json
params.should eq({"name": "Serdar"}) json_params.should eq({"name": "Serdar"})
end end
it "parses request body for array" do it "parses request body for array" do
@ -55,8 +70,8 @@ describe "ParamParser" do
headers: HTTP::Headers{"Content-Type": "application/json"}, headers: HTTP::Headers{"Content-Type": "application/json"},
) )
params = Kemal::ParamParser.new(request).parse json_params = Kemal::ParamParser.new(request).json
params.should eq({"_json": [1]}) json_params.should eq({"_json": [1]})
end end
it "parses request body and query params" do it "parses request body and query params" do
@ -69,8 +84,11 @@ describe "ParamParser" do
headers: HTTP::Headers{"Content-Type": "application/json"}, headers: HTTP::Headers{"Content-Type": "application/json"},
) )
params = Kemal::ParamParser.new(request).parse query_params = Kemal::ParamParser.new(request).query
params.should eq({"foo": "bar", "_json": [1]}) query_params.should eq({"foo": "bar"})
json_params = Kemal::ParamParser.new(request).json
json_params.should eq({"_json": [1]})
end end
it "handles no request body" do it "handles no request body" do
@ -82,17 +100,26 @@ describe "ParamParser" do
headers: HTTP::Headers{"Content-Type": "application/json"}, headers: HTTP::Headers{"Content-Type": "application/json"},
) )
params = Kemal::ParamParser.new(request).parse url_params = Kemal::ParamParser.new(request).url
params.should eq({} of String => AllParamTypes) url_params.should eq({} of String => String)
query_params = Kemal::ParamParser.new(request).query
query_params.should eq({} of String => String)
body_params = Kemal::ParamParser.new(request).body
body_params.should eq({} of String => String)
json_params = Kemal::ParamParser.new(request).json
json_params.should eq({} of String => AllParamTypes)
end end
end end
context "when content type is incorrect" do context "when content type is incorrect" do
it "does not parse request body" do it "does not parse request body" do
route = Route.new "POST", "/" do |env| route = Route.new "POST", "/" do |env|
name = env.params["name"] name = env.params.body["name"]
age = env.params["age"] age = env.params.body["age"]
hasan = env.params["hasan"] hasan = env.params.query["hasan"]
"Hello #{name} #{hasan} #{age}" "Hello #{name} #{hasan} #{age}"
end end
@ -103,8 +130,11 @@ describe "ParamParser" do
headers: HTTP::Headers{"Content-Type": "text/plain"}, headers: HTTP::Headers{"Content-Type": "text/plain"},
) )
params = Kemal::ParamParser.new(request).parse query_params = Kemal::ParamParser.new(request).query
params.should eq({"hasan" => "cemal"}) query_params.should eq({"hasan" => "cemal"})
body_params = Kemal::ParamParser.new(request).body
body_params.should eq({} of String => String)
end end
end end
end end

View file

@ -15,7 +15,7 @@ describe "Kemal::RouteHandler" do
it "routes request with query string" do it "routes request with query string" do
kemal = Kemal::RouteHandler::INSTANCE kemal = Kemal::RouteHandler::INSTANCE
kemal.add_route "GET", "/" do |env| kemal.add_route "GET", "/" do |env|
"hello #{env.params["message"]}" "hello #{env.params.query["message"]}"
end end
request = HTTP::Request.new("GET", "/?message=world") request = HTTP::Request.new("GET", "/?message=world")
io_with_context = create_request_and_return_io(kemal, request) io_with_context = create_request_and_return_io(kemal, request)
@ -26,7 +26,7 @@ describe "Kemal::RouteHandler" do
it "routes request with multiple query strings" do it "routes request with multiple query strings" do
kemal = Kemal::RouteHandler::INSTANCE kemal = Kemal::RouteHandler::INSTANCE
kemal.add_route "GET", "/" do |env| kemal.add_route "GET", "/" do |env|
"hello #{env.params["message"]} time #{env.params["time"]}" "hello #{env.params.query["message"]} time #{env.params.query["time"]}"
end end
request = HTTP::Request.new("GET", "/?message=world&time=now") request = HTTP::Request.new("GET", "/?message=world&time=now")
io_with_context = create_request_and_return_io(kemal, request) io_with_context = create_request_and_return_io(kemal, request)
@ -37,7 +37,7 @@ describe "Kemal::RouteHandler" do
it "route parameter has more precedence than query string arguments" do it "route parameter has more precedence than query string arguments" do
kemal = Kemal::RouteHandler::INSTANCE kemal = Kemal::RouteHandler::INSTANCE
kemal.add_route "GET", "/:message" do |env| kemal.add_route "GET", "/:message" do |env|
"hello #{env.params["message"]}" "hello #{env.params.url["message"]}"
end end
request = HTTP::Request.new("GET", "/world?message=coco") request = HTTP::Request.new("GET", "/world?message=coco")
io_with_context = create_request_and_return_io(kemal, request) io_with_context = create_request_and_return_io(kemal, request)
@ -48,8 +48,8 @@ describe "Kemal::RouteHandler" do
it "parses simple JSON body" do it "parses simple JSON body" do
kemal = Kemal::RouteHandler::INSTANCE kemal = Kemal::RouteHandler::INSTANCE
kemal.add_route "POST", "/" do |env| kemal.add_route "POST", "/" do |env|
name = env.params["name"] name = env.params.json["name"]
age = env.params["age"] age = env.params.json["age"]
"Hello #{name} Age #{age}" "Hello #{name} Age #{age}"
end end
@ -68,7 +68,7 @@ describe "Kemal::RouteHandler" do
it "parses JSON with string array" do it "parses JSON with string array" do
kemal = Kemal::RouteHandler::INSTANCE kemal = Kemal::RouteHandler::INSTANCE
kemal.add_route "POST", "/" do |env| kemal.add_route "POST", "/" do |env|
skills = env.params["skills"] as Array skills = env.params.json["skills"] as Array
"Skills #{skills.each.join(',')}" "Skills #{skills.each.join(',')}"
end end
@ -87,7 +87,7 @@ describe "Kemal::RouteHandler" do
it "parses JSON with json object array" do it "parses JSON with json object array" do
kemal = Kemal::RouteHandler::INSTANCE kemal = Kemal::RouteHandler::INSTANCE
kemal.add_route "POST", "/" do |env| kemal.add_route "POST", "/" do |env|
skills = env.params["skills"] as Array skills = env.params.json["skills"] as Array
skills_from_languages = skills.map do |skill| skills_from_languages = skills.map do |skill|
skill = skill as Hash skill = skill as Hash
skill["language"] skill["language"]
@ -170,8 +170,8 @@ describe "Kemal::RouteHandler" do
request = HTTP::Request.new( request = HTTP::Request.new(
"POST", "POST",
"/", "/",
body: json_payload.to_json, body: "_method=DELETE",
headers: HTTP::Headers{"Content-Type": "application/json"} headers: HTTP::Headers{"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"}
) )
io_with_context = create_request_and_return_io(kemal, request) io_with_context = create_request_and_return_io(kemal, request)
client_response = HTTP::Client::Response.from_io(io_with_context, decompress: false) client_response = HTTP::Client::Response.from_io(io_with_context, decompress: false)

View file

@ -8,7 +8,7 @@ describe "Views" do
it "renders file" do it "renders file" do
kemal = Kemal::RouteHandler::INSTANCE kemal = Kemal::RouteHandler::INSTANCE
kemal.add_route "GET", "/view/:name" do |env| kemal.add_route "GET", "/view/:name" do |env|
name = env.params["name"] name = env.params.url["name"]
render "spec/asset/hello.ecr" render "spec/asset/hello.ecr"
end end
request = HTTP::Request.new("GET", "/view/world") request = HTTP::Request.new("GET", "/view/world")
@ -20,7 +20,7 @@ describe "Views" do
it "renders file with dynamic variables" do it "renders file with dynamic variables" do
kemal = Kemal::RouteHandler::INSTANCE kemal = Kemal::RouteHandler::INSTANCE
kemal.add_route "GET", "/view/:name" do |env| kemal.add_route "GET", "/view/:name" do |env|
name = env.params["name"] name = env.params.url["name"]
render_with_base_and_layout "hello.ecr" render_with_base_and_layout "hello.ecr"
end end
request = HTTP::Request.new("GET", "/view/world") request = HTTP::Request.new("GET", "/view/world")
@ -32,7 +32,7 @@ describe "Views" do
it "renders layout" do it "renders layout" do
kemal = Kemal::RouteHandler::INSTANCE kemal = Kemal::RouteHandler::INSTANCE
kemal.add_route "GET", "/view/:name" do |env| kemal.add_route "GET", "/view/:name" do |env|
name = env.params["name"] name = env.params.url["name"]
render "spec/asset/hello.ecr", "spec/asset/layout.ecr" render "spec/asset/hello.ecr", "spec/asset/layout.ecr"
end end
request = HTTP::Request.new("GET", "/view/world") request = HTTP::Request.new("GET", "/view/world")

View file

@ -18,7 +18,7 @@ at_exit do
# This route serves the built-in images for not_found and exceptions. # This route serves the built-in images for not_found and exceptions.
get "/__kemal__/:image" do |env| get "/__kemal__/:image" do |env|
image = env.params["image"] image = env.params.url["image"]
file_path = File.expand_path("libs/kemal/images/#{image}", Dir.current) file_path = File.expand_path("libs/kemal/images/#{image}", Dir.current)
if File.exists? file_path if File.exists? file_path
env.response.headers.add "Content-Type", "application/octet-stream" env.response.headers.add "Content-Type", "application/octet-stream"

View file

@ -4,7 +4,7 @@ class HTTP::Server
class Context class Context
def params def params
@request.url_params = route_lookup.params @request.url_params = route_lookup.params
@params ||= Kemal::ParamParser.new(@request).parse @params ||= Kemal::ParamParser.new(@request)
end end
def redirect(url, status_code = 302) def redirect(url, status_code = 302)

View file

@ -10,34 +10,37 @@ class Kemal::ParamParser
APPLICATION_JSON = "application/json" APPLICATION_JSON = "application/json"
def initialize(@request) def initialize(@request)
@params = {} of String => AllParamTypes @url = {} of String => String
@query = {} of String => String
@body = {} of String => String
@json = {} of String => AllParamTypes
end end
def parse {% for method in %w(url query body json) %}
parse_request def {{method.id}}
end # check memoization
return @{{method.id}} if @{{method.id}}_parsed
def parse_request parse_{{method.id}}
parse_query # memoize
parse_body @{{method.id}}_parsed = true
parse_json @{{method.id}}
parse_url_params
@params
end end
{% end %}
def parse_body def parse_body
return if (@request.headers["Content-Type"]? =~ /#{URL_ENCODED_FORM}/).nil? return if (@request.headers["Content-Type"]? =~ /#{URL_ENCODED_FORM}/).nil?
parse_part(@request.body) @body = parse_part(@request.body)
end end
def parse_query def parse_query
parse_part(@request.query) @query = parse_part(@request.query)
end end
def parse_url_params def parse_url
if params = @request.url_params if params = @request.url_params
params.each do |key, value| params.each do |key, value|
@params[key] = value @url[key as String] = value as String
end end
end end
end end
@ -52,18 +55,21 @@ class Kemal::ParamParser
body = @request.body as String body = @request.body as String
case json = JSON.parse(body).raw case json = JSON.parse(body).raw
when Hash when Hash
json.each do |k, v| json.each do |key, value|
@params[k as String] = v as AllParamTypes @json[key as String] = value as AllParamTypes
end end
when Array when Array
@params["_json"] = json @json["_json"] = json
end end
end end
def parse_part(part) def parse_part(part)
return unless part part_params = {} of String => String
HTTP::Params.parse(part) do |key, value| if part
@params[key] ||= value HTTP::Params.parse(part) do |key, value|
part_params[key as String] ||= value as String
end
end end
part_params
end end
end end

View file

@ -11,7 +11,7 @@ class HTTP::Request
private def check_for_method_override! private def check_for_method_override!
@override_method = @method @override_method = @method
if @method == "POST" if @method == "POST"
params = Kemal::ParamParser.new(self).parse_request params = Kemal::ParamParser.new(self).body
if params.has_key?("_method") && HTTP::Request.override_method_valid?(params["_method"]) if params.has_key?("_method") && HTTP::Request.override_method_valid?(params["_method"])
@override_method = params["_method"] @override_method = params["_method"]
end end