From 25dab4d2c76dd6174355a4642ad081eef77f684a Mon Sep 17 00:00:00 2001 From: Marius Date: Mon, 1 Feb 2016 20:35:34 +0100 Subject: [PATCH 1/4] Accept IO objects as HTTP request bodies --- spec/std/http/client/client_spec.cr | 8 ++++++++ src/http/client/client.cr | 16 ++++++++-------- src/http/request.cr | 8 +++++--- 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/spec/std/http/client/client_spec.cr b/spec/std/http/client/client_spec.cr index a1bc5dedc19e..2c3285a61512 100644 --- a/spec/std/http/client/client_spec.cr +++ b/spec/std/http/client/client_spec.cr @@ -87,5 +87,13 @@ module HTTP client.get("/") end end + + it "accepts IO objects as body" do + body = MemoryIO.new("hello world") + + TestServer.open("localhost", 0, 0) do |server| + Client.exec("POST", "http://localhost:#{server.addr.ip_port}/", nil, body) + end + end end end diff --git a/src/http/client/client.cr b/src/http/client/client.cr index 8df60bc0ce17..b84b4e28daf3 100644 --- a/src/http/client/client.cr +++ b/src/http/client/client.cr @@ -193,7 +193,7 @@ class HTTP::Client # response = client.{{method.id}}("/", headers: HTTP::Headers{"User-agent": "AwesomeApp"}, body: "Hello!") # response.body #=> "..." # ``` - def {{method.id}}(path, headers = nil : HTTP::Headers?, body = nil : String?) : HTTP::Client::Response + def {{method.id}}(path, headers = nil : HTTP::Headers?, body = nil : String | IO | Nil) : HTTP::Client::Response exec {{method.upcase}}, path, headers, body end @@ -206,7 +206,7 @@ class HTTP::Client # response.body_io.gets #=> "..." # end # ``` - def {{method.id}}(path, headers = nil : HTTP::Headers?, body = nil : String?) + def {{method.id}}(path, headers = nil : HTTP::Headers?, body = nil : String | IO | Nil) exec {{method.upcase}}, path, headers, body do |response| yield response end @@ -219,7 +219,7 @@ class HTTP::Client # response = HTTP::Client.{{method.id}}("/", headers: HTTP::Headers{"User-agent": "AwesomeApp"}, body: "Hello!") # response.body #=> "..." # ``` - def self.{{method.id}}(url : String | URI, headers = nil : HTTP::Headers?, body = nil : String?) : HTTP::Client::Response + def self.{{method.id}}(url : String | URI, headers = nil : HTTP::Headers?, body = nil : String | IO | Nil) : HTTP::Client::Response exec {{method.upcase}}, url, headers, body end @@ -231,7 +231,7 @@ class HTTP::Client # response.body_io.gets #=> "..." # end # ``` - def self.{{method.id}}(url : String | URI, headers = nil : HTTP::Headers?, body = nil : String?) + def self.{{method.id}}(url : String | URI, headers = nil : HTTP::Headers?, body = nil : String | IO | Nil) exec {{method.upcase}}, url, headers, body do |response| yield response end @@ -348,7 +348,7 @@ class HTTP::Client # response = client.exec "GET", "/" # response.body # => "..." # ``` - def exec(method : String, path, headers = nil : HTTP::Headers?, body = nil : String?) : HTTP::Client::Response + def exec(method : String, path, headers = nil : HTTP::Headers?, body = nil : String | IO | Nil) : HTTP::Client::Response exec new_request method, path, headers, body end @@ -361,7 +361,7 @@ class HTTP::Client # response.body_io.gets # => "..." # end # ``` - def exec(method : String, path, headers = nil : HTTP::Headers?, body = nil : String?) + def exec(method : String, path, headers = nil : HTTP::Headers?, body = nil : String | IO | Nil) exec(new_request(method, path, headers, body)) do |response| yield response end @@ -374,7 +374,7 @@ class HTTP::Client # response = HTTP::Client.exec "GET", "http://www.example.com" # response.body # => "..." # ``` - def self.exec(method, url : String | URI, headers = nil : HTTP::Headers?, body = nil : String?) : HTTP::Client::Response + def self.exec(method, url : String | URI, headers = nil : HTTP::Headers?, body = nil : String | IO | Nil) : HTTP::Client::Response exec(url) do |client, path| client.exec method, path, headers, body end @@ -388,7 +388,7 @@ class HTTP::Client # response.body_io.gets # => "..." # end # ``` - def self.exec(method, url : String | URI, headers = nil : HTTP::Headers?, body = nil : String?) + def self.exec(method, url : String | URI, headers = nil : HTTP::Headers?, body = nil : String | IO | Nil) exec(url) do |client, path| client.exec(method, path, headers, body) do |response| yield response diff --git a/src/http/request.cr b/src/http/request.cr index 9684429c65d8..9b0e50367c58 100644 --- a/src/http/request.cr +++ b/src/http/request.cr @@ -8,12 +8,14 @@ class HTTP::Request getter body getter version - def initialize(@method : String, @resource, @headers = Headers.new : Headers, @body = nil, @version = "HTTP/1.1") - if body = @body + def initialize(@method : String, @resource, @headers = Headers.new : Headers, body = nil, @version = "HTTP/1.1") + if body.is_a?(String) @headers["Content-Length"] = body.bytesize.to_s - elsif @method == "POST" || @method == "PUT" + elsif !body && (@method == "POST" || @method == "PUT") @headers["Content-Length"] = "0" end + + @body = body end # Returns a convenience wrapper around querying and setting cookie related From 96d549fc8008020a2ecec10ad9278084ac9d5a74 Mon Sep 17 00:00:00 2001 From: Marius Date: Mon, 1 Feb 2016 20:54:31 +0100 Subject: [PATCH 2/4] Add test for chunked HTTP request --- spec/std/http/request_spec.cr | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/spec/std/http/request_spec.cr b/spec/std/http/request_spec.cr index 9114ad95d181..580d107faac1 100644 --- a/spec/std/http/request_spec.cr +++ b/spec/std/http/request_spec.cr @@ -66,6 +66,14 @@ module HTTP io.to_s.should eq("POST / HTTP/1.1\r\nContent-Length: 13\r\n\r\nthisisthebody") end + it "serialize chunked POST (with IO as body)" do + body = MemoryIO.new "hello world" + request = Request.new "POST", "/", body: body + io = MemoryIO.new + request.to_io(io) + io.to_s.should eq("POST / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\nb\r\nhello world\r\n0\r\n\r\n") + end + it "parses GET" do request = Request.from_io(MemoryIO.new("GET / HTTP/1.1\r\nHost: host.example.org\r\n\r\n")).not_nil! request.method.should eq("GET") From e9bf3166420fc89f0c482f17b4a3dfca2c1dcf86 Mon Sep 17 00:00:00 2001 From: Marius Date: Wed, 3 Feb 2016 14:35:29 +0100 Subject: [PATCH 3/4] Always convert body to string for signing --- src/oauth/signature.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/oauth/signature.cr b/src/oauth/signature.cr index 55633c60478d..86859481ab7a 100644 --- a/src/oauth/signature.cr +++ b/src/oauth/signature.cr @@ -80,7 +80,7 @@ struct OAuth::Signature body = request.body content_type = request.headers["Content-type"]? if body && content_type == "application/x-www-form-urlencoded" - params.add_query body + params.add_query body.to_s end params From f7fc155d3ba55a3eb4a80535cd9b1c297798a529 Mon Sep 17 00:00:00 2001 From: Marius Date: Wed, 3 Feb 2016 16:35:14 +0100 Subject: [PATCH 4/4] Read body IO as string for signing params --- src/oauth/signature.cr | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/oauth/signature.cr b/src/oauth/signature.cr index 86859481ab7a..82b0b6a0894b 100644 --- a/src/oauth/signature.cr +++ b/src/oauth/signature.cr @@ -80,7 +80,11 @@ struct OAuth::Signature body = request.body content_type = request.headers["Content-type"]? if body && content_type == "application/x-www-form-urlencoded" - params.add_query body.to_s + if body.is_a?(IO) + body = body.gets_to_end + end + + params.add_query body end params