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 +4 -0
- data/README.md +2 -2
- data/em-http-request.gemspec +4 -4
- data/examples/digest_auth/client.rb +25 -0
- data/examples/digest_auth/server.rb +28 -0
- data/examples/fibered-http.rb +1 -1
- data/lib/em-http/client.rb +13 -6
- data/lib/em-http/decoders.rb +138 -29
- data/lib/em-http/http_client_options.rb +6 -5
- data/lib/em-http/http_connection.rb +9 -4
- data/lib/em-http/http_connection_options.rb +13 -2
- data/lib/em-http/http_header.rb +21 -1
- data/lib/em-http/middleware/digest_auth.rb +112 -0
- data/lib/em-http/middleware/oauth2.rb +28 -0
- data/lib/em-http/request.rb +1 -0
- data/lib/em-http/version.rb +1 -1
- data/spec/client_spec.rb +62 -7
- data/spec/digest_auth_spec.rb +48 -0
- data/spec/external_spec.rb +2 -2
- data/spec/fixtures/gzip-sample.gz +0 -0
- data/spec/gzip_spec.rb +68 -0
- data/spec/helper.rb +1 -0
- data/spec/middleware/oauth2_spec.rb +15 -0
- data/spec/pipelining_spec.rb +2 -2
- data/spec/redirect_spec.rb +66 -12
- data/spec/socksify_proxy_spec.rb +36 -0
- data/spec/stallion.rb +7 -0
- metadata +20 -10
data/Changelog.md
CHANGED
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/
|
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
|
data/em-http-request.gemspec
CHANGED
@@ -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.
|
18
|
-
s.add_dependency "addressable", ">= 2.
|
19
|
-
s.add_dependency "http_parser.rb", ">= 0.
|
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
|
data/examples/fibered-http.rb
CHANGED
@@ -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
|
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) }
|
data/lib/em-http/client.rb
CHANGED
@@ -80,7 +80,7 @@ module EventMachine
|
|
80
80
|
end
|
81
81
|
|
82
82
|
def redirect?
|
83
|
-
@response_header.
|
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
|
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
|
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
|
data/lib/em-http/decoders.rb
CHANGED
@@ -91,52 +91,161 @@ module EventMachine::HttpDecoders
|
|
91
91
|
end
|
92
92
|
end
|
93
93
|
|
94
|
-
|
95
|
-
|
96
|
-
|
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
|
100
|
-
@
|
101
|
-
|
103
|
+
def finished?
|
104
|
+
@state == :finish
|
105
|
+
end
|
102
106
|
|
103
|
-
|
104
|
-
if @
|
105
|
-
@
|
106
|
-
@
|
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
|
111
|
-
|
112
|
-
@
|
113
|
-
@
|
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
|
-
|
120
|
-
|
121
|
-
|
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
|
-
|
125
|
-
@
|
210
|
+
if finished?
|
211
|
+
compressed[(@pos - pos)..-1]
|
212
|
+
else
|
213
|
+
""
|
126
214
|
end
|
215
|
+
end
|
216
|
+
end
|
127
217
|
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
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
|
-
|
137
|
-
|
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
|
-
|
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,
|
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
|
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.
|
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
|