em-http-request 0.2.13 → 0.2.14

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of em-http-request might be problematic. Click here for more details.

data/Changelog.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.14 / 2010-10-06
4
+
5
+ - bugfix: form-encode keys/values of ruby objects passed in as body
6
+
3
7
  ## 0.2.13 / 2010-09-25
4
8
 
5
9
  - added SOCKS5 proxy support
data/README.md CHANGED
@@ -29,6 +29,7 @@ Libraries & Applications using em-http
29
29
  - [RDaneel](http://github.com/hasmanydevelopers/RDaneel) - Ruby crawler which respects robots.txt
30
30
  - [rsolr-async](http://github.com/mwmitchell/rsolr-async) - An asynchronus connection adapter for RSolr
31
31
  - [PubSubHubbub](http://github.com/igrigorik/PubSubHubbub) - Asynchronous PubSubHubbub ruby client
32
+ - [Firering](http://github.com/EmmanuelOga/firering) - Eventmachine powered Campfire API
32
33
  - and many others.. drop me a link if you want yours included!
33
34
 
34
35
  Simple client example
data/Rakefile CHANGED
@@ -19,9 +19,9 @@ task :default => :compile
19
19
  Rake::RDocTask.new(:rdoc) do |task|
20
20
  task.rdoc_dir = 'doc'
21
21
  task.title = 'EventMachine::HttpRequest'
22
- task.options = %w(--title HttpRequest --main README --line-numbers)
22
+ task.options = %w(--title HttpRequest --main README.md --line-numbers)
23
23
  task.rdoc_files.include(['lib/**/*.rb'])
24
- task.rdoc_files.include(['README', 'LICENSE'])
24
+ task.rdoc_files.include(['README.md', 'LICENSE'])
25
25
  end
26
26
 
27
27
  # Rebuild parser Ragel
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.13
1
+ 0.2.14
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{em-http-request}
8
- s.version = "0.2.13"
8
+ s.version = "0.2.14"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Ilya Grigorik"]
12
- s.date = %q{2010-09-25}
12
+ s.date = %q{2010-10-06}
13
13
  s.description = %q{EventMachine based, async HTTP Request interface}
14
14
  s.email = %q{ilya@igvita.com}
15
15
  s.extensions = ["ext/buffer/extconf.rb", "ext/http11_client/extconf.rb"]
@@ -43,14 +43,13 @@ Gem::Specification.new do |s|
43
43
  "lib/em-http.rb",
44
44
  "lib/em-http/client.rb",
45
45
  "lib/em-http/core_ext/bytesize.rb",
46
- "lib/em-http/core_ext/hash.rb",
47
46
  "lib/em-http/decoders.rb",
48
47
  "lib/em-http/http_options.rb",
49
48
  "lib/em-http/mock.rb",
50
49
  "lib/em-http/multi.rb",
51
50
  "lib/em-http/request.rb",
51
+ "spec/encoding_spec.rb",
52
52
  "spec/fixtures/google.ca",
53
- "spec/hash_spec.rb",
54
53
  "spec/helper.rb",
55
54
  "spec/mock_spec.rb",
56
55
  "spec/multi_spec.rb",
@@ -67,7 +66,7 @@ Gem::Specification.new do |s|
67
66
  s.rubygems_version = %q{1.3.7}
68
67
  s.summary = %q{EventMachine based, async HTTP Request interface}
69
68
  s.test_files = [
70
- "spec/hash_spec.rb",
69
+ "spec/encoding_spec.rb",
71
70
  "spec/helper.rb",
72
71
  "spec/mock_spec.rb",
73
72
  "spec/multi_spec.rb",
@@ -1 +1 @@
1
- require 'em-http'
1
+ require 'em-http'
data/lib/em-http.rb CHANGED
@@ -10,7 +10,6 @@ require 'socket'
10
10
  require File.dirname(__FILE__) + '/http11_client'
11
11
  require File.dirname(__FILE__) + '/em_buffer'
12
12
 
13
- require File.dirname(__FILE__) + '/em-http/core_ext/hash'
14
13
  require File.dirname(__FILE__) + '/em-http/core_ext/bytesize'
15
14
 
16
15
  require File.dirname(__FILE__) + '/em-http/client'
@@ -82,9 +82,9 @@ module EventMachine
82
82
 
83
83
  # Escapes a URI.
84
84
  def escape(s)
85
- s.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/n) {
85
+ s.to_s.gsub(/([^a-zA-Z0-9_.-]+)/n) {
86
86
  '%'+$1.unpack('H2'*bytesize($1)).join('%').upcase
87
- }.tr(' ', '+')
87
+ }
88
88
  end
89
89
 
90
90
  # Unescapes a URI escaped string.
@@ -153,6 +153,29 @@ module EventMachine
153
153
  end
154
154
  end
155
155
 
156
+ def form_encode_body(obj)
157
+ pairs = []
158
+ recursive = Proc.new do |h, prefix|
159
+ h.each do |k,v|
160
+ key = prefix == '' ? escape(k) : "#{prefix}[#{escape(k)}]"
161
+
162
+ if v.is_a? Array
163
+ nh = Hash.new
164
+ v.size.times { |t| nh[t] = v[t] }
165
+ recursive.call(nh, key)
166
+
167
+ elsif v.is_a? Hash
168
+ recursive.call(v, key)
169
+ else
170
+ pairs << "#{key}=#{escape(v)}"
171
+ end
172
+ end
173
+ end
174
+
175
+ recursive.call(obj, '')
176
+ return pairs.join('&')
177
+ end
178
+
156
179
  # Encode a field in an HTTP header
157
180
  def encode_field(k, v)
158
181
  FIELD_ENCODING % [k, v]
@@ -236,8 +259,8 @@ module EventMachine
236
259
  @state = :connect_socks_proxy
237
260
  send_socks_handshake
238
261
 
239
- # if we need to negotiate the proxy connection first, then
240
- # issue a CONNECT query and wait for 200 response
262
+ # if we need to negotiate the proxy connection first, then
263
+ # issue a CONNECT query and wait for 200 response
241
264
  elsif connect_proxy? and @state == :response_header
242
265
  @state = :connect_http_proxy
243
266
  send_request_header
@@ -308,7 +331,7 @@ module EventMachine
308
331
  def normalize_body
309
332
  @normalized_body ||= begin
310
333
  if @options[:body].is_a? Hash
311
- @options[:body].to_params
334
+ form_encode_body(@options[:body])
312
335
  else
313
336
  @options[:body]
314
337
  end
@@ -845,4 +868,3 @@ module EventMachine
845
868
  end
846
869
 
847
870
  end
848
-
@@ -1,122 +1,122 @@
1
- require 'zlib'
2
- require 'stringio'
3
-
4
- ##
5
- # Provides a unified callback interface to decompression libraries.
6
- module EventMachine::HttpDecoders
7
-
8
- class DecoderError < StandardError
9
- end
10
-
11
- class << self
12
- def accepted_encodings
13
- DECODERS.inject([]) { |r,d| r + d.encoding_names }
14
- end
15
-
16
- def decoder_for_encoding(encoding)
17
- DECODERS.each { |d|
18
- return d if d.encoding_names.include? encoding
19
- }
20
- nil
21
- end
22
- end
23
-
24
- class Base
25
- def self.encoding_names
26
- name = to_s.split('::').last.downcase
27
- [name]
28
- end
29
-
30
- ##
31
- # chunk_callback:: [Block] To handle a decompressed chunk
32
- def initialize(&chunk_callback)
33
- @chunk_callback = chunk_callback
34
- end
35
-
36
- def <<(compressed)
37
- return unless compressed && compressed.size > 0
38
-
39
- decompressed = decompress(compressed)
40
- receive_decompressed decompressed
41
- end
42
-
43
- def finalize!
44
- decompressed = finalize
45
- receive_decompressed decompressed
46
- end
47
-
48
- private
49
-
50
- def receive_decompressed(decompressed)
51
- if decompressed && decompressed.size > 0
52
- @chunk_callback.call(decompressed)
53
- end
54
- end
55
-
56
- protected
57
-
58
- ##
59
- # Must return a part of decompressed
60
- def decompress(compressed)
61
- nil
62
- end
63
-
64
- ##
65
- # May return last part
66
- def finalize
67
- nil
68
- end
69
- end
70
-
71
- class Deflate < Base
72
- def decompress(compressed)
73
- begin
74
- @zstream ||= Zlib::Inflate.new(nil)
75
- @zstream.inflate(compressed)
76
- rescue Zlib::Error
77
- raise DecoderError
78
- end
79
- end
80
-
81
- def finalize
82
- return nil unless @zstream
83
-
84
- begin
85
- r = @zstream.inflate(nil)
86
- @zstream.close
87
- r
88
- rescue Zlib::Error
89
- raise DecoderError
90
- end
91
- end
92
- end
93
-
94
- ##
95
- # Oneshot decompressor, due to lack of a streaming Gzip reader
96
- # implementation. We may steal code from Zliby to improve this.
97
- #
98
- # For now, do not put `gzip' or `compressed' in your accept-encoding
99
- # header if you expect much data through the :on_response interface.
100
- class GZip < Base
101
- def self.encoding_names
102
- %w(gzip compressed)
103
- end
104
-
105
- def decompress(compressed)
106
- @buf ||= ''
107
- @buf += compressed
108
- nil
109
- end
110
-
111
- def finalize
112
- begin
113
- Zlib::GzipReader.new(StringIO.new(@buf.to_s)).read
114
- rescue Zlib::Error
115
- raise DecoderError
116
- end
117
- end
118
- end
119
-
120
- DECODERS = [Deflate, GZip]
121
-
122
- end
1
+ require 'zlib'
2
+ require 'stringio'
3
+
4
+ ##
5
+ # Provides a unified callback interface to decompression libraries.
6
+ module EventMachine::HttpDecoders
7
+
8
+ class DecoderError < StandardError
9
+ end
10
+
11
+ class << self
12
+ def accepted_encodings
13
+ DECODERS.inject([]) { |r,d| r + d.encoding_names }
14
+ end
15
+
16
+ def decoder_for_encoding(encoding)
17
+ DECODERS.each { |d|
18
+ return d if d.encoding_names.include? encoding
19
+ }
20
+ nil
21
+ end
22
+ end
23
+
24
+ class Base
25
+ def self.encoding_names
26
+ name = to_s.split('::').last.downcase
27
+ [name]
28
+ end
29
+
30
+ ##
31
+ # chunk_callback:: [Block] To handle a decompressed chunk
32
+ def initialize(&chunk_callback)
33
+ @chunk_callback = chunk_callback
34
+ end
35
+
36
+ def <<(compressed)
37
+ return unless compressed && compressed.size > 0
38
+
39
+ decompressed = decompress(compressed)
40
+ receive_decompressed decompressed
41
+ end
42
+
43
+ def finalize!
44
+ decompressed = finalize
45
+ receive_decompressed decompressed
46
+ end
47
+
48
+ private
49
+
50
+ def receive_decompressed(decompressed)
51
+ if decompressed && decompressed.size > 0
52
+ @chunk_callback.call(decompressed)
53
+ end
54
+ end
55
+
56
+ protected
57
+
58
+ ##
59
+ # Must return a part of decompressed
60
+ def decompress(compressed)
61
+ nil
62
+ end
63
+
64
+ ##
65
+ # May return last part
66
+ def finalize
67
+ nil
68
+ end
69
+ end
70
+
71
+ class Deflate < Base
72
+ def decompress(compressed)
73
+ begin
74
+ @zstream ||= Zlib::Inflate.new(nil)
75
+ @zstream.inflate(compressed)
76
+ rescue Zlib::Error
77
+ raise DecoderError
78
+ end
79
+ end
80
+
81
+ def finalize
82
+ return nil unless @zstream
83
+
84
+ begin
85
+ r = @zstream.inflate(nil)
86
+ @zstream.close
87
+ r
88
+ rescue Zlib::Error
89
+ raise DecoderError
90
+ end
91
+ end
92
+ end
93
+
94
+ ##
95
+ # Oneshot decompressor, due to lack of a streaming Gzip reader
96
+ # implementation. We may steal code from Zliby to improve this.
97
+ #
98
+ # For now, do not put `gzip' or `compressed' in your accept-encoding
99
+ # header if you expect much data through the :on_response interface.
100
+ class GZip < Base
101
+ def self.encoding_names
102
+ %w(gzip compressed)
103
+ end
104
+
105
+ def decompress(compressed)
106
+ @buf ||= ''
107
+ @buf += compressed
108
+ nil
109
+ end
110
+
111
+ def finalize
112
+ begin
113
+ Zlib::GzipReader.new(StringIO.new(@buf.to_s)).read
114
+ rescue Zlib::Error
115
+ raise DecoderError
116
+ end
117
+ end
118
+ end
119
+
120
+ DECODERS = [Deflate, GZip]
121
+
122
+ end
@@ -0,0 +1,34 @@
1
+ require 'spec/helper'
2
+
3
+ describe EventMachine::HttpEncoding do
4
+ include EventMachine::HttpEncoding
5
+
6
+ it "should transform a basic hash into HTTP POST Params" do
7
+ form_encode_body({:a => "alpha", :b => "beta"}).should == "a=alpha&b=beta"
8
+ end
9
+
10
+ it "should transform a more complex hash into HTTP POST Params" do
11
+ form_encode_body({:a => "a", :b => ["c", "d", "e"]}).should == "a=a&b[0]=c&b[1]=d&b[2]=e"
12
+ end
13
+
14
+ it "should transform a very complex hash into HTTP POST Params" do
15
+ params = form_encode_body({:a => "a", :b => [{:c => "c", :d => "d"}, {:e => "e", :f => "f"}]})
16
+ params.should == "a=a&b[0][c]=c&b[0][d]=d&b[1][e]=e&b[1][f]=f"
17
+ end
18
+
19
+ it "should escape values" do
20
+ params = form_encode_body({:stuff => 'string&string'})
21
+ params.should == "stuff=string%26string"
22
+ end
23
+
24
+ it "should escape keys" do
25
+ params = form_encode_body({'bad&str'=> {'key&key' => [:a, :b]}})
26
+ params.should == 'bad%26str[key%26key][0]=a&bad%26str[key%26key][1]=b'
27
+ end
28
+
29
+ it "should escape keys and values" do
30
+ params = form_encode_body({'bad&str'=> {'key&key' => ['bad+&stuff', '[test]']}})
31
+ params.should == "bad%26str[key%26key][0]=bad%2B%26stuff&bad%26str[key%26key][1]=%5Btest%5D"
32
+ end
33
+
34
+ end
data/spec/request_spec.rb CHANGED
@@ -247,6 +247,19 @@ describe EventMachine::HttpRequest do
247
247
  }
248
248
  end
249
249
 
250
+ it "should escape body on POST" do
251
+ EventMachine.run {
252
+ http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/').post :body => {:stuff => 'string&string'}
253
+
254
+ http.errback { failed }
255
+ http.callback {
256
+ http.response_header.status.should == 200
257
+ http.response.should == "stuff=string%26string"
258
+ EventMachine.stop
259
+ }
260
+ }
261
+ end
262
+
250
263
  it "should perform successfull POST with Ruby Hash/Array as params" do
251
264
  EventMachine.run {
252
265
  http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/').post :body => {"key1" => 1, "key2" => [2,3]}
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 2
8
- - 13
9
- version: 0.2.13
8
+ - 14
9
+ version: 0.2.14
10
10
  platform: ruby
11
11
  authors:
12
12
  - Ilya Grigorik
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-09-25 00:00:00 -04:00
17
+ date: 2010-10-06 00:00:00 -04:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
@@ -83,14 +83,13 @@ files:
83
83
  - lib/em-http.rb
84
84
  - lib/em-http/client.rb
85
85
  - lib/em-http/core_ext/bytesize.rb
86
- - lib/em-http/core_ext/hash.rb
87
86
  - lib/em-http/decoders.rb
88
87
  - lib/em-http/http_options.rb
89
88
  - lib/em-http/mock.rb
90
89
  - lib/em-http/multi.rb
91
90
  - lib/em-http/request.rb
91
+ - spec/encoding_spec.rb
92
92
  - spec/fixtures/google.ca
93
- - spec/hash_spec.rb
94
93
  - spec/helper.rb
95
94
  - spec/mock_spec.rb
96
95
  - spec/multi_spec.rb
@@ -133,7 +132,7 @@ signing_key:
133
132
  specification_version: 3
134
133
  summary: EventMachine based, async HTTP Request interface
135
134
  test_files:
136
- - spec/hash_spec.rb
135
+ - spec/encoding_spec.rb
137
136
  - spec/helper.rb
138
137
  - spec/mock_spec.rb
139
138
  - spec/multi_spec.rb
@@ -1,53 +0,0 @@
1
- class Hash
2
- # Stolen partially from Merb : http://noobkit.com/show/ruby/gems/development/merb/hash/to_params.html
3
- # Convert this hash to a query string:
4
- #
5
- # { :name => "Bob",
6
- # :address => {
7
- # :street => '111 Ruby Ave.',
8
- # :city => 'Ruby Central',
9
- # :phones => ['111-111-1111', '222-222-2222']
10
- # }
11
- # }.to_params
12
- # #=> "name=Bob&address[city]=Ruby Central&address[phones]=111-111-1111222-222-2222&address[street]=111 Ruby Ave."
13
- #
14
- def to_params
15
- params = ''
16
- stack = []
17
-
18
- each do |k, v|
19
- if v.is_a?(Hash)
20
- stack << [k,v]
21
- elsif v.is_a?(Array)
22
- stack << [k,Hash.from_array(v)]
23
- else
24
- params << "#{k}=#{v}&"
25
- end
26
- end
27
-
28
- stack.each do |parent, hash|
29
- hash.each do |k, v|
30
- if v.is_a?(Hash)
31
- stack << ["#{parent}[#{k}]", v]
32
- else
33
- params << "#{parent}[#{k}]=#{v}&"
34
- end
35
- end
36
- end
37
-
38
- params.chop! # trailing &
39
- params
40
- end
41
-
42
- ##
43
- # Builds a hash from an array with keys as array indices.
44
- def self.from_array(array = [])
45
- h = Hash.new
46
- array.size.times do |t|
47
- h[t] = array[t]
48
- end
49
- h
50
- end
51
-
52
- end
53
-
data/spec/hash_spec.rb DELETED
@@ -1,24 +0,0 @@
1
- require 'spec/helper'
2
-
3
- describe Hash do
4
-
5
- describe ".to_params" do
6
- it "should transform a basic hash into HTTP POST Params" do
7
- {:a => "alpha", :b => "beta"}.to_params.split("&").should include "a=alpha"
8
- {:a => "alpha", :b => "beta"}.to_params.split("&").should include "b=beta"
9
- end
10
-
11
- it "should transform a more complex hash into HTTP POST Params" do
12
- {:a => "a", :b => ["c", "d", "e"]}.to_params.split("&").should include "a=a"
13
- {:a => "a", :b => ["c", "d", "e"]}.to_params.split("&").should include "b[0]=c"
14
- {:a => "a", :b => ["c", "d", "e"]}.to_params.split("&").should include "b[1]=d"
15
- {:a => "a", :b => ["c", "d", "e"]}.to_params.split("&").should include "b[2]=e"
16
- end
17
-
18
- it "should transform a very complex hash into HTTP POST Params" do
19
- params = {:a => "a", :b => [{:c => "c", :d => "d"}, {:e => "e", :f => "f"}]}.to_params.split("&")
20
- params.should include "a=a"
21
- params.should include "b[0][d]=d"
22
- end
23
- end
24
- end