em-http-request 1.0.0.beta.3 → 1.0.0.beta.4

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,50 +1,35 @@
1
- # Courtesy of Darcy Laycock:
2
- # http://gist.github.com/265261
3
- #
4
-
5
- require 'rubygems'
1
+ $: << 'lib' << '../lib'
6
2
 
7
3
  require 'em-http'
8
- require 'oauth'
9
-
10
- # At a minimum, require 'oauth/request_proxy/em_http_request'
11
- # for this example, we'll use Net::HTTP like support.
12
- require 'oauth/client/em_http'
4
+ require 'em-http/middleware/oauth'
5
+ require 'em-http/middleware/json_response'
13
6
 
14
- # You need two things: an oauth consumer and an access token.
15
- # You need to generate an access token, I suggest looking elsewhere how to do that or wait for a full tutorial.
16
- # For a consumer key / consumer secret, signup for an app at:
17
- # http://twitter.com/apps/new
7
+ require 'pp'
18
8
 
19
- # Edit in your details.
20
- CONSUMER_KEY = ""
21
- CONSUMER_SECRET = ""
22
- ACCESS_TOKEN = ""
23
- ACCESS_TOKEN_SECRET = ""
24
-
25
- def twitter_oauth_consumer
26
- @twitter_oauth_consumer ||= OAuth::Consumer.new(CONSUMER_KEY, CONSUMER_SECRET, :site => "http://twitter.com")
27
- end
28
-
29
- def twitter_oauth_access_token
30
- @twitter_oauth_access_token ||= OAuth::AccessToken.new(twitter_oauth_consumer, ACCESS_TOKEN, ACCESS_TOKEN_SECRET)
31
- end
9
+ OAuthConfig = {
10
+ :consumer_key => '',
11
+ :consumer_secret => '',
12
+ :access_token => '',
13
+ :access_token_secret => ''
14
+ }
32
15
 
33
16
  EM.run do
17
+ # automatically parse the JSON response into a Ruby object
18
+ EventMachine::HttpRequest.use EventMachine::Middleware::JSONResponse
34
19
 
35
- request = EventMachine::HttpRequest.new('http://twitter.com/statuses/update.json')
36
- http = request.post(:body => {'status' => 'Hello Twitter from em-http-request with OAuth'}, :head => {"Content-Type" => "application/x-www-form-urlencoded"}) do |client|
37
- twitter_oauth_consumer.sign!(client, twitter_oauth_access_token)
38
- end
20
+ # sign the request with OAuth credentials
21
+ conn = EventMachine::HttpRequest.new('http://api.twitter.com/1/statuses/home_timeline.json')
22
+ conn.use EventMachine::Middleware::OAuth, OAuthConfig
39
23
 
24
+ http = conn.get
40
25
  http.callback do
41
- puts "Response: #{http.response} (Code: #{http.response_header.status})"
42
- EM.stop_event_loop
26
+ pp http.response
27
+ EM.stop
43
28
  end
44
29
 
45
30
  http.errback do
46
- puts "Failed to post"
47
- EM.stop_event_loop
31
+ puts "Failed retrieving user stream."
32
+ pp http.response
33
+ EM.stop
48
34
  end
49
-
50
- end
35
+ end
data/lib/em-http.rb CHANGED
@@ -10,8 +10,9 @@ require 'em-http/core_ext/bytesize'
10
10
  require 'em-http/http_connection'
11
11
  require 'em-http/http_header'
12
12
  require 'em-http/http_encoding'
13
- require 'em-http/http_options'
13
+ require 'em-http/http_client_options'
14
+ require 'em-http/http_connection_options'
14
15
  require 'em-http/client'
15
16
  require 'em-http/multi'
16
17
  require 'em-http/request'
17
- require 'em-http/decoders'
18
+ require 'em-http/decoders'
@@ -1,4 +1,5 @@
1
1
  module EventMachine
2
+
2
3
  class HttpClient
3
4
  include Deferrable
4
5
  include HttpEncoding
@@ -17,17 +18,15 @@ module EventMachine
17
18
  CRLF="\r\n"
18
19
 
19
20
  attr_accessor :state, :response
20
- attr_reader :response_header, :error, :content_charset, :req
21
+ attr_reader :response_header, :error, :content_charset, :req, :cookies
21
22
 
22
- def initialize(conn, req, options)
23
+ def initialize(conn, options)
23
24
  @conn = conn
24
-
25
- @req = req
26
- @method = req.method
27
- @options = options
25
+ @req = options
28
26
 
29
27
  @stream = nil
30
28
  @headers = nil
29
+ @cookies = []
31
30
 
32
31
  reset!
33
32
  end
@@ -43,14 +42,15 @@ module EventMachine
43
42
  end
44
43
 
45
44
  def last_effective_url; @req.uri; end
46
- def redirects; @req.options[:followed]; end
45
+ def redirects; @req.followed; end
46
+ def peer; @conn.peer; end
47
47
 
48
48
  def connection_completed
49
49
  @state = :response_header
50
50
 
51
- head, body = build_request, @options[:body]
51
+ head, body = build_request, @req.body
52
52
  @conn.middleware.each do |m|
53
- head, body = m.request(head, body) if m.respond_to?(:request)
53
+ head, body = m.request(self, head, body) if m.respond_to?(:request)
54
54
  end
55
55
 
56
56
  send_request(head, body)
@@ -66,6 +66,10 @@ module EventMachine
66
66
  unbind
67
67
  end
68
68
 
69
+ def continue?
70
+ @response_header.status == 100 && (@req.method == 'POST' || @req.method == 'PUT')
71
+ end
72
+
69
73
  def finished?
70
74
  @state == :finished || (@state == :body && @response_header.content_length.nil?)
71
75
  end
@@ -77,8 +81,26 @@ module EventMachine
77
81
  def unbind
78
82
  if finished?
79
83
  if redirect?
80
- @req.options[:followed] += 1
81
- @conn.redirect(self, @response_header.location)
84
+
85
+ begin
86
+ @conn.middleware.each do |m|
87
+ m.response(self) if m.respond_to?(:response)
88
+ end
89
+
90
+ # one of the injected middlewares could have changed
91
+ # our redirect settings, check if we still want to
92
+ # follow the location header
93
+ if redirect?
94
+ @req.followed += 1
95
+ @req.set_uri(@response_header.location)
96
+ @conn.redirect(self)
97
+ else
98
+ succeed(self)
99
+ end
100
+
101
+ rescue Exception => e
102
+ on_error(e.message)
103
+ end
82
104
  else
83
105
  succeed(self)
84
106
  end
@@ -101,32 +123,22 @@ module EventMachine
101
123
  body.is_a?(Hash) ? form_encode_body(body) : body
102
124
  end
103
125
 
104
- def proxy?; !@options[:proxy].nil?; end
105
- def http_proxy?; proxy? && [nil, :http].include?(@options[:proxy][:type]); end
106
-
107
- def ssl?; @req.uri.scheme == "https" || @req.uri.port == 443; end
108
-
109
- def continue?
110
- @response_header.status == 100 && (@method == 'POST' || @method == 'PUT')
111
- end
112
-
113
126
  def build_request
114
- head = @options[:head] ? munge_header_keys(@options[:head]) : {}
115
- proxy = @options[:proxy]
127
+ head = @req.headers ? munge_header_keys(@req.headers) : {}
128
+ proxy = @req.proxy
116
129
 
117
- if http_proxy?
118
- # initialize headers for the http proxy
119
- head = proxy[:head] ? munge_header_keys(proxy[:head]) : {}
120
- head['proxy-authorization'] = proxy[:authorization] if proxy[:authorization]
130
+ if @req.http_proxy?
131
+ head['proxy-authorization'] = @req.proxy[:authorization] if @req.proxy[:authorization]
121
132
  end
122
133
 
123
134
  # Set the cookie header if provided
124
- if cookie = head.delete('cookie')
125
- head['cookie'] = encode_cookie(cookie)
135
+ if cookie = head['cookie']
136
+ @cookies << encode_cookie(cookie) if cookie
126
137
  end
138
+ head['cookie'] = @cookies.compact.uniq.join("; ").squeeze(";") unless @cookies.empty?
127
139
 
128
140
  # Set connection close unless keepalive
129
- unless @options[:keepalive]
141
+ if !@req.keepalive
130
142
  head['connection'] = 'close'
131
143
  end
132
144
 
@@ -141,8 +153,8 @@ module EventMachine
141
153
 
142
154
  def send_request(head, body)
143
155
  body = normalize_body(body)
144
- file = @options[:file]
145
- query = @options[:query]
156
+ file = @req.file
157
+ query = @req.query
146
158
 
147
159
  # Set the Content-Length if file is given
148
160
  head['content-length'] = File.size(file) if file
@@ -151,19 +163,19 @@ module EventMachine
151
163
  head['content-length'] = body.bytesize if body
152
164
 
153
165
  # Set content-type header if missing and body is a Ruby hash
154
- if not head['content-type'] and @options[:body].is_a? Hash
166
+ if not head['content-type'] and @req.body.is_a? Hash
155
167
  head['content-type'] = 'application/x-www-form-urlencoded'
156
168
  end
157
169
 
158
- request_header ||= encode_request(@method, @req.uri, query, @conn.opts.proxy)
170
+ request_header ||= encode_request(@req.method, @req.uri, query, @conn.connopts.proxy)
159
171
  request_header << encode_headers(head)
160
172
  request_header << CRLF
161
173
  @conn.send_data request_header
162
174
 
163
175
  if body
164
176
  @conn.send_data body
165
- elsif @options[:file]
166
- @conn.stream_file_data @options[:file], :http_chunks => false
177
+ elsif @req.file
178
+ @conn.stream_file_data @req.file, :http_chunks => false
167
179
  end
168
180
  end
169
181
 
@@ -207,6 +219,9 @@ module EventMachine
207
219
  return
208
220
  end
209
221
 
222
+ # add set-cookie's to cookie list
223
+ @cookies << @response_header.cookie if @response_header.cookie && @req.pass_cookies
224
+
210
225
  # correct location header - some servers will incorrectly give a relative URI
211
226
  if @response_header.location
212
227
  begin
@@ -229,7 +244,7 @@ module EventMachine
229
244
  # Fire callbacks immediately after recieving header requests
230
245
  # if the request method is HEAD. In case of a redirect, terminate
231
246
  # current connection and reinitialize the process.
232
- if @method == "HEAD"
247
+ if @req.method == "HEAD"
233
248
  @state = :finished
234
249
  return
235
250
  end
@@ -242,7 +257,7 @@ module EventMachine
242
257
  @state = :body
243
258
  end
244
259
 
245
- if decoder_class = HttpDecoders.decoder_for_encoding(response_header[CONTENT_ENCODING])
260
+ if @req.decoding && decoder_class = HttpDecoders.decoder_for_encoding(response_header[CONTENT_ENCODING])
246
261
  begin
247
262
  @content_decoder = decoder_class.new do |s| on_decoded_body_data(s) end
248
263
  rescue HttpDecoders::DecoderError
@@ -71,7 +71,7 @@ module EventMachine::HttpDecoders
71
71
  class Deflate < Base
72
72
  def decompress(compressed)
73
73
  begin
74
- @zstream ||= Zlib::Inflate.new(nil)
74
+ @zstream ||= Zlib::Inflate.new(-Zlib::MAX_WBITS)
75
75
  @zstream.inflate(compressed)
76
76
  rescue Zlib::Error
77
77
  raise DecoderError
@@ -0,0 +1,56 @@
1
+ class HttpClientOptions
2
+ attr_reader :uri, :method, :host, :port, :proxy
3
+ attr_reader :headers, :file, :body, :query, :path
4
+ attr_reader :keepalive, :pass_cookies, :decoding
5
+
6
+ attr_accessor :followed, :redirects
7
+
8
+ def initialize(uri, options, method)
9
+ @keepalive = options[:keepalive] || false # default to single request per connection
10
+ @redirects = options[:redirects] ||= 0 # default number of redirects to follow
11
+ @followed = options[:followed] ||= 0 # keep track of number of followed requests
12
+
13
+ @method = method.to_s.upcase
14
+ @headers = options[:head] || {}
15
+ @proxy = options[:proxy] || {}
16
+ @query = options[:query]
17
+ @path = options[:path]
18
+
19
+ @file = options[:file]
20
+ @body = options[:body]
21
+
22
+ @pass_cookies = options.fetch(:pass_cookies, true) # pass cookies between redirects
23
+ @decoding = options.fetch(:decoding, true) # auto-decode compressed response
24
+
25
+ set_uri(uri)
26
+ end
27
+
28
+ def follow_redirect?; @followed < @redirects; end
29
+ def http_proxy?; @proxy && [nil, :http].include?(@proxy[:type]); end
30
+ def ssl?; @uri.scheme == "https" || @uri.port == 443; end
31
+
32
+ def set_uri(uri)
33
+ uri = uri.kind_of?(Addressable::URI) ? uri : Addressable::URI::parse(uri.to_s)
34
+ uri.path = '/' if uri.path.empty?
35
+ uri.path = @path if @path
36
+
37
+ @uri = uri
38
+
39
+ # Make sure the ports are set as Addressable::URI doesn't
40
+ # set the port if it isn't there
41
+ if @uri.scheme == "https"
42
+ @uri.port ||= 443
43
+ else
44
+ @uri.port ||= 80
45
+ end
46
+
47
+ if !@proxy.empty?
48
+ @host = @proxy[:host]
49
+ @port = @proxy[:port]
50
+ else
51
+ @host = @uri.host
52
+ @port = @uri.port
53
+ end
54
+
55
+ end
56
+ end
@@ -8,40 +8,90 @@ module EventMachine
8
8
  def post options = {}, &blk; setup_request(:post, options, &blk); end
9
9
  end
10
10
 
11
- class FailedConnection
12
- include HTTPMethods
11
+ class HttpStubConnection < Connection
13
12
  include Deferrable
13
+ attr_reader :parent
14
14
 
15
- attr_accessor :error, :opts
15
+ def parent=(p)
16
+ @parent = p
17
+ @parent.conn = self
18
+ end
16
19
 
17
- def initialize(req)
18
- @opts = req
20
+ def receive_data(data)
21
+ @parent.receive_data data
19
22
  end
20
23
 
21
- def setup_request(method, options)
22
- c = HttpClient.new(self, HttpOptions.new(@opts.uri, options, method), options)
23
- c.close(@error)
24
- c
24
+ def connection_completed
25
+ @parent.connection_completed
26
+ end
27
+
28
+ def unbind
29
+ @parent.unbind
25
30
  end
26
31
  end
27
32
 
28
- class HttpConnection < Connection
33
+ class HttpConnection
29
34
  include HTTPMethods
30
- include Deferrable
31
35
  include Socksify
32
36
 
33
- attr_accessor :error, :opts
37
+ attr_reader :deferred
38
+ attr_accessor :error, :connopts, :uri, :conn
34
39
 
35
- def setup_request(method, options = {})
36
- c = HttpClient.new(self, HttpOptions.new(@opts.uri, options, method), options)
37
- callback { c.connection_completed }
40
+ def initialize
41
+ @deferred = true
42
+ @middleware = []
43
+ end
44
+
45
+ def conn=(c)
46
+ @conn = c
47
+ @deferred = false
48
+ end
49
+
50
+ def activate_connection(client)
51
+ begin
52
+ EventMachine.connect(@connopts.host, @connopts.port, HttpStubConnection) do |conn|
53
+ post_init
54
+
55
+ @deferred = false
56
+ @conn = conn
57
+
58
+ conn.parent = self
59
+ conn.pending_connect_timeout = @connopts.connect_timeout
60
+ conn.comm_inactivity_timeout = @connopts.inactivity_timeout
61
+ end
62
+
63
+ finalize_request(client)
64
+ rescue EventMachine::ConnectionError => e
65
+ #
66
+ # Currently, this can only fire on initial connection setup
67
+ # since #connect is a synchronous method. Hence, rescue the
68
+ # exception, and return a failed deferred which will immediately
69
+ # fail any client request.
70
+ #
71
+ # Once there is async-DNS, then we'll iterate over the outstanding
72
+ # client requests and fail them in order.
73
+ #
74
+ # Net outcome: failed connection will invoke the same ConnectionError
75
+ # message on the connection deferred, and on the client deferred.
76
+ #
77
+ client.close(e.message)
78
+ end
79
+ end
80
+
81
+ def setup_request(method, options = {}, c = nil)
82
+ c ||= HttpClient.new(self, HttpClientOptions.new(@uri, options, method))
83
+ @deferred ? activate_connection(c) : finalize_request(c)
84
+ c
85
+ end
86
+
87
+ def finalize_request(c)
88
+ @conn.callback { c.connection_completed }
38
89
 
39
90
  middleware.each do |m|
40
91
  c.callback &m.method(:response) if m.respond_to?(:response)
41
92
  end
42
93
 
43
94
  @clients.push c
44
- c
45
95
  end
46
96
 
47
97
  def middleware
@@ -52,19 +102,17 @@ module EventMachine
52
102
  @clients = []
53
103
  @pending = []
54
104
 
55
- @middleware = []
56
-
57
105
  @p = Http::Parser.new
58
106
  @p.on_headers_complete = proc do |h|
59
- @clients.first.parse_response_header(h, @p.http_version, @p.status_code)
107
+ client.parse_response_header(h, @p.http_version, @p.status_code)
60
108
  end
61
109
 
62
110
  @p.on_body = proc do |b|
63
- @clients.first.on_body_data(b)
111
+ client.on_body_data(b)
64
112
  end
65
113
 
66
114
  @p.on_message_complete = proc do
67
- if not @clients.first.continue?
115
+ if not client.continue?
68
116
  c = @clients.shift
69
117
  c.state = :finished
70
118
  c.on_request_complete
@@ -72,8 +120,12 @@ module EventMachine
72
120
  end
73
121
  end
74
122
 
75
- def use(klass)
76
- @middleware << klass
123
+ def use(klass, *args, &block)
124
+ @middleware << klass.new(*args, &block)
125
+ end
126
+
127
+ def peer
128
+ Socket.unpack_sockaddr_in(@peer)[1] rescue nil
77
129
  end
78
130
 
79
131
  def receive_data(data)
@@ -81,35 +133,31 @@ module EventMachine
81
133
  @p << data
82
134
  rescue HTTP::Parser::Error => e
83
135
  c = @clients.shift
84
- c.on_error(e.message)
136
+ c.nil? ? unbind : c.on_error(e.message)
85
137
  end
86
138
  end
87
139
 
88
140
  def connection_completed
89
- if @opts.proxy && @opts.proxy[:type] == :socks5
90
- socksify(client.req.uri.host, client.req.uri.port, *@opts.proxy[:authorization]) { start }
141
+ @peer = @conn.get_peername
91
142
 
143
+ if @connopts.proxy && @connopts.proxy[:type] == :socks5
144
+ socksify(client.req.uri.host, client.req.uri.port, *@connopts.proxy[:authorization]) { start }
92
145
  else
93
146
  start
94
147
  end
95
148
  end
96
149
 
97
150
  def start
98
- ssl = @opts.options[:tls] || @opts.options[:ssl] || {}
99
- start_tls(ssl) if client && client.ssl?
100
-
101
- succeed
151
+ @conn.start_tls(@connopts.tls) if client && client.req.ssl?
152
+ @conn.succeed
102
153
  end
103
154
 
104
- def redirect(client, location)
105
- client.req.set_uri(location)
155
+ def redirect(client)
106
156
  @pending.push client
107
- rescue Exception => e
108
- client.on_error(e.message)
109
157
  end
110
158
 
111
159
  def unbind
112
- @clients.map {|c| c.unbind }
160
+ @clients.map { |c| c.unbind }
113
161
 
114
162
  if r = @pending.shift
115
163
  @clients.push r
@@ -118,19 +166,27 @@ module EventMachine
118
166
  @p.reset!
119
167
 
120
168
  begin
121
- set_deferred_status :unknown
122
- reconnect(r.req.host, r.req.port)
123
- callback { r.connection_completed }
169
+ @conn.set_deferred_status :unknown
170
+ @conn.reconnect(r.req.host, r.req.port)
171
+ @conn.callback { r.connection_completed }
124
172
  rescue EventMachine::ConnectionError => e
125
173
  @clients.pop.close(e.message)
126
174
  end
127
175
  end
128
176
  end
129
177
 
178
+ def send_data(data)
179
+ @conn.send_data data
180
+ end
181
+
182
+ def stream_file_data(filename, args = {})
183
+ @conn.stream_file_data filename, args
184
+ end
185
+
130
186
  private
131
187
 
132
- def client
133
- @clients.first
134
- end
188
+ def client
189
+ @clients.first
190
+ end
135
191
  end
136
192
  end