em-http-request 1.0.1 → 1.0.2

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.

@@ -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