em-http-request 1.0.1 → 1.0.2

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.

@@ -1,51 +1,51 @@
1
- $: << 'lib' << '../lib'
2
-
3
- require 'eventmachine'
4
- require 'em-http'
5
- require 'fiber'
6
-
7
- # Using Fibers in Ruby 1.9 to simulate blocking IO / IO scheduling
8
- # while using the async EventMachine API's
9
-
10
- def async_fetch(url)
11
- f = Fiber.current
12
- http = EventMachine::HttpRequest.new(url).get :timeout => 10
13
-
14
- http.callback { f.resume(http) }
15
- http.errback { f.resume(http) }
16
-
17
- Fiber.yield
18
-
19
- if http.error
20
- p [:HTTP_ERROR, http.error]
21
- end
22
-
23
- http
24
- end
25
-
26
- EventMachine.run do
27
- Fiber.new{
28
-
29
- puts "Setting up HTTP request #1"
30
- data = async_fetch('http://0.0.0.0/')
31
- puts "Fetched page #1: #{data.response_header.status}"
32
-
33
- puts "Setting up HTTP request #2"
34
- data = async_fetch('http://www.yahoo.com/')
35
- puts "Fetched page #2: #{data.response_header.status}"
36
-
37
- puts "Setting up HTTP request #3"
38
- data = async_fetch('http://non-existing.domain/')
39
- puts "Fetched page #3: #{data.response_header.status}"
40
-
41
- EventMachine.stop
42
- }.resume
43
- end
44
-
45
- puts "Done"
46
-
47
- # Setting up HTTP request #1
48
- # Fetched page #1: 302
49
- # Setting up HTTP request #2
50
- # Fetched page #2: 200
51
- # Done
1
+ $: << 'lib' << '../lib'
2
+
3
+ require 'eventmachine'
4
+ require 'em-http'
5
+ require 'fiber'
6
+
7
+ # Using Fibers in Ruby 1.9 to simulate blocking IO / IO scheduling
8
+ # while using the async EventMachine API's
9
+
10
+ def async_fetch(url)
11
+ f = Fiber.current
12
+ http = EventMachine::HttpRequest.new(url).get :timeout => 10
13
+
14
+ http.callback { f.resume(http) }
15
+ http.errback { f.resume(http) }
16
+
17
+ Fiber.yield
18
+
19
+ if http.error
20
+ p [:HTTP_ERROR, http.error]
21
+ end
22
+
23
+ http
24
+ end
25
+
26
+ EventMachine.run do
27
+ Fiber.new{
28
+
29
+ puts "Setting up HTTP request #1"
30
+ data = async_fetch('http://0.0.0.0/')
31
+ puts "Fetched page #1: #{data.response_header.status}"
32
+
33
+ puts "Setting up HTTP request #2"
34
+ data = async_fetch('http://www.yahoo.com/')
35
+ puts "Fetched page #2: #{data.response_header.status}"
36
+
37
+ puts "Setting up HTTP request #3"
38
+ data = async_fetch('http://non-existing.domain/')
39
+ puts "Fetched page #3: #{data.response_header.status}"
40
+
41
+ EventMachine.stop
42
+ }.resume
43
+ end
44
+
45
+ puts "Done"
46
+
47
+ # Setting up HTTP request #1
48
+ # Fetched page #1: 302
49
+ # Setting up HTTP request #2
50
+ # Fetched page #2: 200
51
+ # Done
@@ -1,307 +1,309 @@
1
- require 'cookiejar'
2
-
3
- module EventMachine
4
-
5
-
6
- class HttpClient
7
- include Deferrable
8
- include HttpEncoding
9
- include HttpStatus
10
-
11
- TRANSFER_ENCODING="TRANSFER_ENCODING"
12
- CONTENT_ENCODING="CONTENT_ENCODING"
13
- CONTENT_LENGTH="CONTENT_LENGTH"
14
- CONTENT_TYPE="CONTENT_TYPE"
15
- LAST_MODIFIED="LAST_MODIFIED"
16
- KEEP_ALIVE="CONNECTION"
17
- SET_COOKIE="SET_COOKIE"
18
- LOCATION="LOCATION"
19
- HOST="HOST"
20
- ETAG="ETAG"
21
-
22
- CRLF="\r\n"
23
-
24
- attr_accessor :state, :response
25
- attr_reader :response_header, :error, :content_charset, :req, :cookies
26
-
27
- def initialize(conn, options)
28
- @conn = conn
29
- @req = options
30
-
31
- @stream = nil
32
- @headers = nil
33
- @cookies = []
34
- @cookiejar = CookieJar.new
35
-
36
- reset!
37
- end
38
-
39
- def reset!
40
- @response_header = HttpResponseHeader.new
41
- @state = :response_header
42
-
43
- @response = ''
44
- @error = nil
45
- @content_decoder = nil
46
- @content_charset = nil
47
- end
48
-
49
- def last_effective_url; @req.uri; end
50
- def redirects; @req.followed; end
51
- def peer; @conn.peer; end
52
-
53
- def connection_completed
54
- @state = :response_header
55
-
56
- head, body = build_request, @req.body
57
- @conn.middleware.each do |m|
58
- head, body = m.request(self, head, body) if m.respond_to?(:request)
59
- end
60
-
61
- send_request(head, body)
62
- end
63
-
64
- def on_request_complete
65
- begin
66
- @content_decoder.finalize! if @content_decoder
67
- rescue HttpDecoders::DecoderError
68
- on_error "Content-decoder error"
69
- end
70
-
71
- unbind
72
- end
73
-
74
- def continue?
75
- @response_header.status == 100 && (@req.method == 'POST' || @req.method == 'PUT')
76
- end
77
-
78
- def finished?
79
- @state == :finished || (@state == :body && @response_header.content_length.nil?)
80
- end
81
-
82
- def redirect?
83
- @response_header.location && @req.follow_redirect?
84
- end
85
-
86
- def unbind(reason = nil)
87
- if finished?
88
- if redirect?
89
-
90
- begin
91
- @conn.middleware.each do |m|
92
- m.response(self) if m.respond_to?(:response)
93
- end
94
-
95
- # one of the injected middlewares could have changed
96
- # our redirect settings, check if we still want to
97
- # follow the location header
98
- if redirect?
99
- @req.followed += 1
100
-
101
- @cookies.clear
102
- @cookies = @cookiejar.get(@response_header.location).map(&:to_s) if @req.pass_cookies
103
- @req.set_uri(@response_header.location)
104
- @conn.redirect(self)
105
- else
106
- succeed(self)
107
- end
108
-
109
- rescue Exception => e
110
- on_error(e.message)
111
- end
112
- else
113
- succeed(self)
114
- end
115
-
116
- else
117
- on_error(reason)
118
- end
119
- end
120
-
121
- def on_error(msg = nil)
122
- @error = msg
123
- fail(self)
124
- end
125
- alias :close :on_error
126
-
127
- def stream(&blk); @stream = blk; end
128
- def headers(&blk); @headers = blk; end
129
-
130
- def normalize_body(body)
131
- body.is_a?(Hash) ? form_encode_body(body) : body
132
- end
133
-
134
- def build_request
135
- head = @req.headers ? munge_header_keys(@req.headers) : {}
136
- proxy = @req.proxy
137
-
138
- if @req.http_proxy?
139
- head['proxy-authorization'] = @req.proxy[:authorization] if @req.proxy[:authorization]
140
- end
141
-
142
- # Set the cookie header if provided
143
- if cookie = head['cookie']
144
- @cookies << encode_cookie(cookie) if cookie
145
- end
146
- head['cookie'] = @cookies.compact.uniq.join("; ").squeeze(";") unless @cookies.empty?
147
-
148
- # Set connection close unless keepalive
149
- if !@req.keepalive
150
- head['connection'] = 'close'
151
- end
152
-
153
- # Set the Host header if it hasn't been specified already
154
- head['host'] ||= encode_host
155
-
156
- # Set the User-Agent if it hasn't been specified
157
- head['user-agent'] ||= "EventMachine HttpClient"
158
-
159
- head
160
- end
161
-
162
- def send_request(head, body)
163
- body = normalize_body(body)
164
- file = @req.file
165
- query = @req.query
166
-
167
- # Set the Content-Length if file is given
168
- head['content-length'] = File.size(file) if file
169
-
170
- # Set the Content-Length if body is given,
171
- # or we're doing an empty post or put
172
- if body
173
- head['content-length'] = body.bytesize
174
- elsif @req.method == 'POST' or @req.method == 'PUT'
175
- # wont happen if body is set and we already set content-length above
176
- head['content-length'] = 0
177
- end
178
-
179
- # Set content-type header if missing and body is a Ruby hash
180
- if not head['content-type'] and @req.body.is_a? Hash
181
- head['content-type'] = 'application/x-www-form-urlencoded'
182
- end
183
-
184
- request_header ||= encode_request(@req.method, @req.uri, query, @conn.connopts.proxy)
185
- request_header << encode_headers(head)
186
- request_header << CRLF
187
- @conn.send_data request_header
188
-
189
- if body
190
- @conn.send_data body
191
- elsif @req.file
192
- @conn.stream_file_data @req.file, :http_chunks => false
193
- end
194
- end
195
-
196
- def on_body_data(data)
197
- if @content_decoder
198
- begin
199
- @content_decoder << data
200
- rescue HttpDecoders::DecoderError
201
- on_error "Content-decoder error"
202
- end
203
- else
204
- on_decoded_body_data(data)
205
- end
206
- end
207
-
208
- def on_decoded_body_data(data)
209
- data.force_encoding @content_charset if @content_charset
210
- if @stream
211
- @stream.call(data)
212
- else
213
- @response << data
214
- end
215
- end
216
-
217
- def parse_response_header(header, version, status)
218
- header.each do |key, val|
219
- @response_header[key.upcase.gsub('-','_')] = val
220
- end
221
-
222
- @response_header.http_version = version.join('.')
223
- @response_header.http_status = status
224
- @response_header.http_reason = CODE[status] || 'unknown'
225
-
226
- # invoke headers callback after full parse
227
- # if one is specified by the user
228
- @headers.call(@response_header) if @headers
229
-
230
- unless @response_header.http_status and @response_header.http_reason
231
- @state = :invalid
232
- on_error "no HTTP response"
233
- return
234
- end
235
-
236
- # add set-cookie's to cookie list
237
- if @response_header.cookie && @req.pass_cookies
238
- [@response_header.cookie].flatten.each {|cookie| @cookiejar.set(cookie, @req.uri)}
239
- end
240
-
241
- # correct location header - some servers will incorrectly give a relative URI
242
- if @response_header.location
243
- begin
244
- location = Addressable::URI.parse(@response_header.location)
245
-
246
- if location.relative?
247
- location = @req.uri.join(location)
248
- @response_header[LOCATION] = location.to_s
249
- else
250
- # if redirect is to an absolute url, check for correct URI structure
251
- raise if location.host.nil?
252
- end
253
-
254
- rescue
255
- on_error "Location header format error"
256
- return
257
- end
258
- end
259
-
260
- # Fire callbacks immediately after recieving header requests
261
- # if the request method is HEAD. In case of a redirect, terminate
262
- # current connection and reinitialize the process.
263
- if @req.method == "HEAD"
264
- @state = :finished
265
- return
266
- end
267
-
268
- if @response_header.chunked_encoding?
269
- @state = :chunk_header
270
- elsif @response_header.content_length
271
- @state = :body
272
- else
273
- @state = :body
274
- end
275
-
276
- if @req.decoding && decoder_class = HttpDecoders.decoder_for_encoding(response_header[CONTENT_ENCODING])
277
- begin
278
- @content_decoder = decoder_class.new do |s| on_decoded_body_data(s) end
279
- rescue HttpDecoders::DecoderError
280
- on_error "Content-decoder error"
281
- end
282
- end
283
-
284
- # handle malformed header - Content-Type repetitions.
285
- content_type = [response_header[CONTENT_TYPE]].flatten.first
286
-
287
- if String.method_defined?(:force_encoding) && /;\s*charset=\s*(.+?)\s*(;|$)/.match(content_type)
288
- @content_charset = Encoding.find($1.gsub(/^\"|\"$/, '')) rescue Encoding.default_external
289
- end
290
- end
291
-
292
- class CookieJar
293
- def initialize
294
- @jar = ::CookieJar::Jar.new
295
- end
296
-
297
- def set string, uri
298
- @jar.set_cookie(uri, string) rescue nil # drop invalid cookies
299
- end
300
-
301
- def get uri
302
- uri = URI.parse(uri) rescue nil
303
- uri ? @jar.get_cookies(uri) : []
304
- end
305
- end # CookieJar
306
- end
307
- end
1
+ require 'cookiejar'
2
+
3
+ module EventMachine
4
+
5
+
6
+ class HttpClient
7
+ include Deferrable
8
+ include HttpEncoding
9
+ include HttpStatus
10
+
11
+ TRANSFER_ENCODING="TRANSFER_ENCODING"
12
+ CONTENT_ENCODING="CONTENT_ENCODING"
13
+ CONTENT_LENGTH="CONTENT_LENGTH"
14
+ CONTENT_TYPE="CONTENT_TYPE"
15
+ LAST_MODIFIED="LAST_MODIFIED"
16
+ KEEP_ALIVE="CONNECTION"
17
+ SET_COOKIE="SET_COOKIE"
18
+ LOCATION="LOCATION"
19
+ HOST="HOST"
20
+ ETAG="ETAG"
21
+
22
+ CRLF="\r\n"
23
+
24
+ attr_accessor :state, :response
25
+ attr_reader :response_header, :error, :content_charset, :req, :cookies
26
+
27
+ def initialize(conn, options)
28
+ @conn = conn
29
+ @req = options
30
+
31
+ @stream = nil
32
+ @headers = nil
33
+ @cookies = []
34
+ @cookiejar = CookieJar.new
35
+
36
+ reset!
37
+ end
38
+
39
+ def reset!
40
+ @response_header = HttpResponseHeader.new
41
+ @state = :response_header
42
+
43
+ @response = ''
44
+ @error = nil
45
+ @content_decoder = nil
46
+ @content_charset = nil
47
+ end
48
+
49
+ def last_effective_url; @req.uri; end
50
+ def redirects; @req.followed; end
51
+ def peer; @conn.peer; end
52
+
53
+ def connection_completed
54
+ @state = :response_header
55
+
56
+ head, body = build_request, @req.body
57
+ @conn.middleware.each do |m|
58
+ head, body = m.request(self, head, body) if m.respond_to?(:request)
59
+ end
60
+
61
+ send_request(head, body)
62
+ end
63
+
64
+ def on_request_complete
65
+ begin
66
+ @content_decoder.finalize! if @content_decoder
67
+ rescue HttpDecoders::DecoderError
68
+ on_error "Content-decoder error"
69
+ end
70
+
71
+ unbind
72
+ end
73
+
74
+ def continue?
75
+ @response_header.status == 100 && (@req.method == 'POST' || @req.method == 'PUT')
76
+ end
77
+
78
+ def finished?
79
+ @state == :finished || (@state == :body && @response_header.content_length.nil?)
80
+ end
81
+
82
+ def redirect?
83
+ @response_header.location && @req.follow_redirect?
84
+ end
85
+
86
+ def unbind(reason = nil)
87
+ if finished?
88
+ if redirect?
89
+
90
+ begin
91
+ @conn.middleware.each do |m|
92
+ m.response(self) if m.respond_to?(:response)
93
+ end
94
+
95
+ # one of the injected middlewares could have changed
96
+ # our redirect settings, check if we still want to
97
+ # follow the location header
98
+ if redirect?
99
+ @req.followed += 1
100
+
101
+ @cookies.clear
102
+ @cookies = @cookiejar.get(@response_header.location).map(&:to_s) if @req.pass_cookies
103
+ @req.set_uri(@response_header.location)
104
+ @conn.redirect(self)
105
+ else
106
+ succeed(self)
107
+ end
108
+
109
+ rescue Exception => e
110
+ on_error(e.message)
111
+ end
112
+ else
113
+ succeed(self)
114
+ end
115
+
116
+ else
117
+ on_error(reason)
118
+ end
119
+ end
120
+
121
+ def on_error(msg = nil)
122
+ @error = msg
123
+ fail(self)
124
+ end
125
+ alias :close :on_error
126
+
127
+ def stream(&blk); @stream = blk; end
128
+ def headers(&blk); @headers = blk; end
129
+
130
+ def normalize_body(body)
131
+ body.is_a?(Hash) ? form_encode_body(body) : body
132
+ end
133
+
134
+ def build_request
135
+ head = @req.headers ? munge_header_keys(@req.headers) : {}
136
+
137
+ if @req.http_proxy?
138
+ head['proxy-authorization'] = @req.proxy[:authorization] if @req.proxy[:authorization]
139
+ end
140
+
141
+ # Set the cookie header if provided
142
+ if cookie = head['cookie']
143
+ @cookies << encode_cookie(cookie) if cookie
144
+ end
145
+ head['cookie'] = @cookies.compact.uniq.join("; ").squeeze(";") unless @cookies.empty?
146
+
147
+ # Set connection close unless keepalive
148
+ if !@req.keepalive
149
+ head['connection'] = 'close'
150
+ end
151
+
152
+ # Set the Host header if it hasn't been specified already
153
+ head['host'] ||= encode_host
154
+
155
+ # Set the User-Agent if it hasn't been specified
156
+ head['user-agent'] ||= "EventMachine HttpClient"
157
+
158
+ # Set the auth from the URI if given
159
+ head['Authorization'] = @req.uri.userinfo.split(/:/, 2) if @req.uri.userinfo
160
+
161
+ head
162
+ end
163
+
164
+ def send_request(head, body)
165
+ body = normalize_body(body)
166
+ file = @req.file
167
+ query = @req.query
168
+
169
+ # Set the Content-Length if file is given
170
+ head['content-length'] = File.size(file) if file
171
+
172
+ # Set the Content-Length if body is given,
173
+ # or we're doing an empty post or put
174
+ if body
175
+ head['content-length'] = body.bytesize
176
+ elsif @req.method == 'POST' or @req.method == 'PUT'
177
+ # wont happen if body is set and we already set content-length above
178
+ head['content-length'] ||= 0
179
+ end
180
+
181
+ # Set content-type header if missing and body is a Ruby hash
182
+ if not head['content-type'] and @req.body.is_a? Hash
183
+ head['content-type'] = 'application/x-www-form-urlencoded'
184
+ end
185
+
186
+ request_header ||= encode_request(@req.method, @req.uri, query, @conn.connopts.proxy)
187
+ request_header << encode_headers(head)
188
+ request_header << CRLF
189
+ @conn.send_data request_header
190
+
191
+ if body
192
+ @conn.send_data body
193
+ elsif @req.file
194
+ @conn.stream_file_data @req.file, :http_chunks => false
195
+ end
196
+ end
197
+
198
+ def on_body_data(data)
199
+ if @content_decoder
200
+ begin
201
+ @content_decoder << data
202
+ rescue HttpDecoders::DecoderError
203
+ on_error "Content-decoder error"
204
+ end
205
+ else
206
+ on_decoded_body_data(data)
207
+ end
208
+ end
209
+
210
+ def on_decoded_body_data(data)
211
+ data.force_encoding @content_charset if @content_charset
212
+ if @stream
213
+ @stream.call(data)
214
+ else
215
+ @response << data
216
+ end
217
+ end
218
+
219
+ def parse_response_header(header, version, status)
220
+ header.each do |key, val|
221
+ @response_header[key.upcase.gsub('-','_')] = val
222
+ end
223
+
224
+ @response_header.http_version = version.join('.')
225
+ @response_header.http_status = status
226
+ @response_header.http_reason = CODE[status] || 'unknown'
227
+
228
+ # invoke headers callback after full parse
229
+ # if one is specified by the user
230
+ @headers.call(@response_header) if @headers
231
+
232
+ unless @response_header.http_status and @response_header.http_reason
233
+ @state = :invalid
234
+ on_error "no HTTP response"
235
+ return
236
+ end
237
+
238
+ # add set-cookie's to cookie list
239
+ if @response_header.cookie && @req.pass_cookies
240
+ [@response_header.cookie].flatten.each {|cookie| @cookiejar.set(cookie, @req.uri)}
241
+ end
242
+
243
+ # correct location header - some servers will incorrectly give a relative URI
244
+ if @response_header.location
245
+ begin
246
+ location = Addressable::URI.parse(@response_header.location)
247
+
248
+ if location.relative?
249
+ location = @req.uri.join(location)
250
+ @response_header[LOCATION] = location.to_s
251
+ else
252
+ # if redirect is to an absolute url, check for correct URI structure
253
+ raise if location.host.nil?
254
+ end
255
+
256
+ rescue
257
+ on_error "Location header format error"
258
+ return
259
+ end
260
+ end
261
+
262
+ # Fire callbacks immediately after recieving header requests
263
+ # if the request method is HEAD. In case of a redirect, terminate
264
+ # current connection and reinitialize the process.
265
+ if @req.method == "HEAD"
266
+ @state = :finished
267
+ return
268
+ end
269
+
270
+ if @response_header.chunked_encoding?
271
+ @state = :chunk_header
272
+ elsif @response_header.content_length
273
+ @state = :body
274
+ else
275
+ @state = :body
276
+ end
277
+
278
+ if @req.decoding && decoder_class = HttpDecoders.decoder_for_encoding(response_header[CONTENT_ENCODING])
279
+ begin
280
+ @content_decoder = decoder_class.new do |s| on_decoded_body_data(s) end
281
+ rescue HttpDecoders::DecoderError
282
+ on_error "Content-decoder error"
283
+ end
284
+ end
285
+
286
+ # handle malformed header - Content-Type repetitions.
287
+ content_type = [response_header[CONTENT_TYPE]].flatten.first
288
+
289
+ if String.method_defined?(:force_encoding) && /;\s*charset=\s*(.+?)\s*(;|$)/.match(content_type)
290
+ @content_charset = Encoding.find($1.gsub(/^\"|\"$/, '')) rescue Encoding.default_external
291
+ end
292
+ end
293
+
294
+ class CookieJar
295
+ def initialize
296
+ @jar = ::CookieJar::Jar.new
297
+ end
298
+
299
+ def set string, uri
300
+ @jar.set_cookie(uri, string) rescue nil # drop invalid cookies
301
+ end
302
+
303
+ def get uri
304
+ uri = URI.parse(uri) rescue nil
305
+ uri ? @jar.get_cookies(uri) : []
306
+ end
307
+ end # CookieJar
308
+ end
309
+ end