em-http-request 1.0.3 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

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
+ ## master
4
+
5
+ - User-Agent header is now removed if set to nil.
6
+
3
7
  ## 1.0.0.beta.1 / 2011-02-20 - The big rewrite
4
8
 
5
9
  - Switched parser from Ragel to http_parser.rb
data/README.md CHANGED
@@ -36,7 +36,7 @@ Several higher-order Ruby projects have incorporated em-http and other Ruby HTTP
36
36
  - [EM-Synchrony](https://github.com/igrigorik/em-synchrony) - Collection of convenience classes and primitives to help untangle evented code (Ruby 1.9 + Fibers).
37
37
  - [Rack-Client](https://github.com/halorgium/rack-client) - Use Rack API for server, test, and client side. Supports Rack middleware!
38
38
  - [Example in action](https://gist.github.com/802391)
39
- - [Faraday](https://github.com/technoweenie/faraday) - Modular HTTP client library using middleware heavily inspired by Rack.
39
+ - [Faraday](https://github.com/lostisland/faraday) - Modular HTTP client library using middleware heavily inspired by Rack.
40
40
  - [Example in action](https://gist.github.com/802395)
41
41
 
42
42
  ## Testing
@@ -59,4 +59,4 @@ Several higher-order Ruby projects have incorporated em-http and other Ruby HTTP
59
59
 
60
60
  ### License
61
61
 
62
- (MIT License) - Copyright (c) 2011 Ilya Grigorik
62
+ (MIT License) - Copyright (c) 2011 Ilya Grigorik
@@ -14,10 +14,10 @@ Gem::Specification.new do |s|
14
14
  s.description = s.summary
15
15
  s.rubyforge_project = "em-http-request"
16
16
 
17
- s.add_dependency "eventmachine", ">= 1.0.0.beta.4"
18
- s.add_dependency "addressable", ">= 2.2.3"
19
- s.add_dependency "http_parser.rb", ">= 0.5.3"
20
- s.add_dependency "em-socksify"
17
+ s.add_dependency "eventmachine", ">= 1.0.3"
18
+ s.add_dependency "addressable", ">= 2.3.4"
19
+ s.add_dependency "http_parser.rb", ">= 0.6.0.beta.2"
20
+ s.add_dependency "em-socksify", ">= 0.3"
21
21
  s.add_dependency "cookiejar"
22
22
 
23
23
  s.add_development_dependency "rspec"
@@ -0,0 +1,25 @@
1
+ $: << 'lib' << '../../lib'
2
+
3
+ require 'em-http'
4
+ require 'em-http/middleware/digest_auth'
5
+
6
+ digest_config = {
7
+ :username => 'digest_username',
8
+ :password => 'digest_password'
9
+ }
10
+
11
+ EM.run do
12
+
13
+ conn_handshake = EM::HttpRequest.new('http://localhost:3000')
14
+ http_handshake = conn_handshake.get
15
+
16
+ http_handshake.callback do
17
+ conn = EM::HttpRequest.new('http://localhost:3000')
18
+ conn.use EM::Middleware::DigestAuth, http_handshake.response_header['WWW_AUTHENTICATE'], digest_config
19
+ http = conn.get
20
+ http.callback do
21
+ puts http.response
22
+ EM.stop
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,28 @@
1
+ require 'webrick'
2
+
3
+ include WEBrick
4
+
5
+ config = { :Realm => 'DigestAuth_REALM' }
6
+
7
+ htdigest = WEBrick::HTTPAuth::Htdigest.new 'my_password_file'
8
+ htdigest.set_passwd config[:Realm], 'digest_username', 'digest_password'
9
+ htdigest.flush
10
+
11
+ config[:UserDB] = htdigest
12
+
13
+ digest_auth = WEBrick::HTTPAuth::DigestAuth.new config
14
+
15
+ class TestServlet < HTTPServlet::AbstractServlet
16
+ def do_GET(req, res)
17
+ @options[0][:authenticator].authenticate req, res
18
+ res.body = "You are authenticated to see the super secret data\n"
19
+ end
20
+ end
21
+
22
+ s = HTTPServer.new(:Port => 3000)
23
+ s.mount('/', TestServlet, {:authenticator => digest_auth})
24
+ trap("INT") do
25
+ File.delete('my_password_file')
26
+ s.shutdown
27
+ end
28
+ s.start
@@ -9,7 +9,7 @@ require 'fiber'
9
9
 
10
10
  def async_fetch(url)
11
11
  f = Fiber.current
12
- http = EventMachine::HttpRequest.new(url).get :timeout => 10
12
+ http = EventMachine::HttpRequest.new(url, :connect_timeout => 10, :inactivity_timeout => 20).get
13
13
 
14
14
  http.callback { f.resume(http) }
15
15
  http.errback { f.resume(http) }
@@ -80,7 +80,7 @@ module EventMachine
80
80
  end
81
81
 
82
82
  def redirect?
83
- @response_header.location && @req.follow_redirect?
83
+ @response_header.redirection? && @req.follow_redirect?
84
84
  end
85
85
 
86
86
  def unbind(reason = nil)
@@ -101,6 +101,7 @@ module EventMachine
101
101
  @cookies.clear
102
102
  @cookies = @cookiejar.get(@response_header.location).map(&:to_s) if @req.pass_cookies
103
103
  @req.set_uri(@response_header.location)
104
+
104
105
  @conn.redirect(self)
105
106
  else
106
107
  succeed(self)
@@ -114,7 +115,7 @@ module EventMachine
114
115
  end
115
116
 
116
117
  else
117
- on_error(reason)
118
+ on_error(reason || 'connection closed by server')
118
119
  end
119
120
  end
120
121
 
@@ -133,7 +134,7 @@ module EventMachine
133
134
 
134
135
  def build_request
135
136
  head = @req.headers ? munge_header_keys(@req.headers) : {}
136
-
137
+
137
138
  if @conn.connopts.http_proxy?
138
139
  proxy = @conn.connopts.proxy
139
140
  head['proxy-authorization'] = proxy[:authorization] if proxy[:authorization]
@@ -154,7 +155,11 @@ module EventMachine
154
155
  head['host'] ||= encode_host
155
156
 
156
157
  # Set the User-Agent if it hasn't been specified
157
- head['user-agent'] ||= "EventMachine HttpClient"
158
+ if !head.key?('user-agent')
159
+ head['user-agent'] = "EventMachine HttpClient"
160
+ elsif head['user-agent'].nil?
161
+ head.delete('user-agent')
162
+ end
158
163
 
159
164
  # Set the auth from the URI if given
160
165
  head['Authorization'] = @req.uri.userinfo.split(/:/, 2) if @req.uri.userinfo
@@ -180,7 +185,7 @@ module EventMachine
180
185
  end
181
186
 
182
187
  # Set content-type header if missing and body is a Ruby hash
183
- if not head['content-type'] and @req.body.is_a? Hash
188
+ if !head['content-type'] and @req.body.is_a? Hash
184
189
  head['content-type'] = 'application/x-www-form-urlencoded'
185
190
  end
186
191
 
@@ -246,15 +251,17 @@ module EventMachine
246
251
  if @response_header.location
247
252
  begin
248
253
  location = Addressable::URI.parse(@response_header.location)
254
+ location.path = "/" if location.path.empty?
249
255
 
250
256
  if location.relative?
251
257
  location = @req.uri.join(location)
252
- @response_header[LOCATION] = location.to_s
253
258
  else
254
259
  # if redirect is to an absolute url, check for correct URI structure
255
260
  raise if location.host.nil?
256
261
  end
257
262
 
263
+ @response_header[LOCATION] = location.to_s
264
+
258
265
  rescue
259
266
  on_error "Location header format error"
260
267
  return
@@ -91,52 +91,161 @@ module EventMachine::HttpDecoders
91
91
  end
92
92
  end
93
93
 
94
- class GZip < Base
95
- def self.encoding_names
96
- %w(gzip compressed)
94
+ ##
95
+ # Partial implementation of RFC 1952 to extract the deflate stream from a gzip file
96
+ class GZipHeader
97
+ def initialize
98
+ @state = :begin
99
+ @data = ""
100
+ @pos = 0
97
101
  end
98
102
 
99
- def decompress(compressed)
100
- @buf ||= LazyStringIO.new
101
- @buf << compressed
103
+ def finished?
104
+ @state == :finish
105
+ end
102
106
 
103
- # Zlib::GzipReader loads input in 2048 byte chunks
104
- if @buf.size > 2048
105
- @gzip ||= Zlib::GzipReader.new @buf
106
- @gzip.readline
107
+ def read(n, buffer)
108
+ if (@pos + n) <= @data.size
109
+ buffer << @data[@pos..(@pos + n - 1)]
110
+ @pos += n
111
+ return true
112
+ else
113
+ return false
107
114
  end
108
115
  end
109
116
 
110
- def finalize
111
- begin
112
- @gzip ||= Zlib::GzipReader.new @buf
113
- @gzip.read
114
- rescue Zlib::Error
115
- raise DecoderError
117
+ def readbyte
118
+ if (@pos + 1) <= @data.size
119
+ @pos += 1
120
+ @data.getbyte(@pos - 1)
116
121
  end
117
122
  end
118
123
 
119
- class LazyStringIO
120
- def initialize(string="")
121
- @stream = string
124
+ def eof?
125
+ @pos >= @data.size
126
+ end
127
+
128
+ def extract_stream(compressed)
129
+ @data << compressed
130
+ pos = @pos
131
+
132
+ while !eof? && !finished?
133
+ buffer = ""
134
+
135
+ case @state
136
+ when :begin
137
+ break if !read(10, buffer)
138
+
139
+ if buffer.getbyte(0) != 0x1f || buffer.getbyte(1) != 0x8b
140
+ raise DecoderError.new("magic header not found")
141
+ end
142
+
143
+ if buffer.getbyte(2) != 0x08
144
+ raise DecoderError.new("unknown compression method")
145
+ end
146
+
147
+ @flags = buffer.getbyte(3)
148
+ if (@flags & 0xe0).nonzero?
149
+ raise DecoderError.new("unknown header flags set")
150
+ end
151
+
152
+ # We don't care about these values, I'm leaving the code for reference
153
+ # @time = buffer[4..7].unpack("V")[0] # little-endian uint32
154
+ # @extra_flags = buffer.getbyte(8)
155
+ # @os = buffer.getbyte(9)
156
+
157
+ @state = :extra_length
158
+
159
+ when :extra_length
160
+ if (@flags & 0x04).nonzero?
161
+ break if !read(2, buffer)
162
+ @extra_length = buffer.unpack("v")[0] # little-endian uint16
163
+ @state = :extra
164
+ else
165
+ @state = :extra
166
+ end
167
+
168
+ when :extra
169
+ if (@flags & 0x04).nonzero?
170
+ break if read(@extra_length, buffer)
171
+ @state = :name
172
+ else
173
+ @state = :name
174
+ end
175
+
176
+ when :name
177
+ if (@flags & 0x08).nonzero?
178
+ while !(buffer = readbyte).nil?
179
+ if buffer == 0
180
+ @state = :comment
181
+ break
182
+ end
183
+ end
184
+ else
185
+ @state = :comment
186
+ end
187
+
188
+ when :comment
189
+ if (@flags & 0x10).nonzero?
190
+ while !(buffer = readbyte).nil?
191
+ if buffer == 0
192
+ @state = :hcrc
193
+ break
194
+ end
195
+ end
196
+ else
197
+ @state = :hcrc
198
+ end
199
+
200
+ when :hcrc
201
+ if (@flags & 0x02).nonzero?
202
+ break if !read(2, buffer)
203
+ @state = :finish
204
+ else
205
+ @state = :finish
206
+ end
207
+ end
122
208
  end
123
209
 
124
- def <<(string)
125
- @stream << string
210
+ if finished?
211
+ compressed[(@pos - pos)..-1]
212
+ else
213
+ ""
126
214
  end
215
+ end
216
+ end
127
217
 
128
- def read(length=nil, buffer=nil)
129
- buffer ||= ""
130
- length ||= 0
131
- buffer << @stream[0..(length-1)]
132
- @stream = @stream[length..-1]
133
- buffer
218
+ class GZip < Base
219
+ def self.encoding_names
220
+ %w(gzip compressed)
221
+ end
222
+
223
+ def decompress(compressed)
224
+ @header ||= GZipHeader.new
225
+ if !@header.finished?
226
+ compressed = @header.extract_stream(compressed)
134
227
  end
135
228
 
136
- def size
137
- @stream.size
229
+ @zstream ||= Zlib::Inflate.new(-Zlib::MAX_WBITS)
230
+ @zstream.inflate(compressed)
231
+ rescue Zlib::Error
232
+ raise DecoderError
233
+ end
234
+
235
+ def finalize
236
+ if @zstream
237
+ if !@zstream.finished?
238
+ r = @zstream.finish
239
+ end
240
+ @zstream.close
241
+ r
242
+ else
243
+ nil
138
244
  end
245
+ rescue Zlib::Error
246
+ raise DecoderError
139
247
  end
248
+
140
249
  end
141
250
 
142
251
  DECODERS = [Deflate, GZip]
@@ -11,9 +11,9 @@ class HttpClientOptions
11
11
  @followed = options[:followed] ||= 0 # keep track of number of followed requests
12
12
 
13
13
  @method = method.to_s.upcase
14
- @headers = options[:head] || {}
14
+ @headers = options[:head] || {}
15
15
  @query = options[:query]
16
- @path = options[:path]
16
+
17
17
 
18
18
  @file = options[:file]
19
19
  @body = options[:body]
@@ -21,19 +21,20 @@ class HttpClientOptions
21
21
  @pass_cookies = options.fetch(:pass_cookies, true) # pass cookies between redirects
22
22
  @decoding = options.fetch(:decoding, true) # auto-decode compressed response
23
23
 
24
- set_uri(uri)
24
+ set_uri(uri, options[:path])
25
25
  end
26
26
 
27
27
  def follow_redirect?; @followed < @redirects; end
28
28
  def ssl?; @uri.scheme == "https" || @uri.port == 443; end
29
29
  def no_body?; @method == "HEAD"; end
30
30
 
31
- def set_uri(uri)
31
+ def set_uri(uri, path = nil)
32
32
  uri = uri.kind_of?(Addressable::URI) ? uri : Addressable::URI::parse(uri.to_s)
33
+ uri.path = path if path
33
34
  uri.path = '/' if uri.path.empty?
34
- uri.path = @path if @path
35
35
 
36
36
  @uri = uri
37
+ @path = uri.path
37
38
 
38
39
  # Make sure the ports are set as Addressable::URI doesn't
39
40
  # set the port if it isn't there
@@ -35,6 +35,7 @@ module EventMachine
35
35
  class HttpConnection
36
36
  include HTTPMethods
37
37
  include Socksify
38
+ include Connectify
38
39
 
39
40
  attr_reader :deferred
40
41
  attr_accessor :error, :connopts, :uri, :conn
@@ -51,7 +52,9 @@ module EventMachine
51
52
 
52
53
  def activate_connection(client)
53
54
  begin
54
- EventMachine.bind_connect(@connopts.bind, @connopts.bind_port, @connopts.host, @connopts.port, HttpStubConnection) do |conn|
55
+ EventMachine.bind_connect(@connopts.bind, @connopts.bind_port,
56
+ @connopts.host, @connopts.port,
57
+ HttpStubConnection) do |conn|
55
58
  post_init
56
59
 
57
60
  @deferred = false
@@ -120,7 +123,7 @@ module EventMachine
120
123
  end
121
124
 
122
125
  @p.on_message_complete = proc do
123
- if not client.continue?
126
+ if !client.continue?
124
127
  c = @clients.shift
125
128
  c.state = :finished
126
129
  c.on_request_complete
@@ -148,8 +151,10 @@ module EventMachine
148
151
  def connection_completed
149
152
  @peer = @conn.get_peername
150
153
 
151
- if @connopts.proxy && @connopts.proxy[:type] == :socks5
154
+ if @connopts.socks_proxy?
152
155
  socksify(client.req.uri.host, client.req.uri.port, *@connopts.proxy[:authorization]) { start }
156
+ elsif @connopts.connect_proxy?
157
+ connectify(client.req.uri.host, client.req.uri.port, *@connopts.proxy[:authorization]) { start }
153
158
  else
154
159
  start
155
160
  end
@@ -164,7 +169,7 @@ module EventMachine
164
169
  @pending.push client
165
170
  end
166
171
 
167
- def unbind(reason)
172
+ def unbind(reason = nil)
168
173
  @clients.map { |c| c.unbind(reason) }
169
174
 
170
175
  if r = @pending.shift