em-http-request 1.0.3 → 1.1.0

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
+ ## 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