em-http-request 1.0.0 → 1.0.1

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.

@@ -28,6 +28,7 @@ class HttpClientOptions
28
28
  def follow_redirect?; @followed < @redirects; end
29
29
  def http_proxy?; @proxy && [nil, :http].include?(@proxy[:type]); end
30
30
  def ssl?; @uri.scheme == "https" || @uri.port == 443; end
31
+ def no_body?; @method == "HEAD"; end
31
32
 
32
33
  def set_uri(uri)
33
34
  uri = uri.kind_of?(Addressable::URI) ? uri : Addressable::URI::parse(uri.to_s)
@@ -1,194 +1,199 @@
1
- module EventMachine
2
-
3
- module HTTPMethods
4
- def get options = {}, &blk; setup_request(:get, options, &blk); end
5
- def head options = {}, &blk; setup_request(:head, options, &blk); end
6
- def delete options = {}, &blk; setup_request(:delete,options, &blk); end
7
- def put options = {}, &blk; setup_request(:put, options, &blk); end
8
- def post options = {}, &blk; setup_request(:post, options, &blk); end
9
- end
10
-
11
- class HttpStubConnection < Connection
12
- include Deferrable
13
- attr_reader :parent
14
-
15
- def parent=(p)
16
- @parent = p
17
- @parent.conn = self
18
- end
19
-
20
- def receive_data(data)
21
- @parent.receive_data data
22
- end
23
-
24
- def connection_completed
25
- @parent.connection_completed
26
- end
27
-
28
- def unbind(reason=nil)
29
- @parent.unbind(reason)
30
- end
31
- end
32
-
33
- class HttpConnection
34
- include HTTPMethods
35
- include Socksify
36
-
37
- attr_reader :deferred
38
- attr_accessor :error, :connopts, :uri, :conn
39
-
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 }
89
-
90
- middleware.each do |m|
91
- c.callback &m.method(:response) if m.respond_to?(:response)
92
- end
93
-
94
- @clients.push c
95
- end
96
-
97
- def middleware
98
- [HttpRequest.middleware, @middleware].flatten
99
- end
100
-
101
- def post_init
102
- @clients = []
103
- @pending = []
104
-
105
- @p = Http::Parser.new
106
- @p.header_value_type = :mixed
107
- @p.on_headers_complete = proc do |h|
108
- client.parse_response_header(h, @p.http_version, @p.status_code)
109
- end
110
-
111
- @p.on_body = proc do |b|
112
- client.on_body_data(b)
113
- end
114
-
115
- @p.on_message_complete = proc do
116
- if not client.continue?
117
- c = @clients.shift
118
- c.state = :finished
119
- c.on_request_complete
120
- end
121
- end
122
- end
123
-
124
- def use(klass, *args, &block)
125
- @middleware << klass.new(*args, &block)
126
- end
127
-
128
- def peer
129
- Socket.unpack_sockaddr_in(@peer)[1] rescue nil
130
- end
131
-
132
- def receive_data(data)
133
- begin
134
- @p << data
135
- rescue HTTP::Parser::Error => e
136
- c = @clients.shift
137
- c.nil? ? unbind : c.on_error(e.message)
138
- end
139
- end
140
-
141
- def connection_completed
142
- @peer = @conn.get_peername
143
-
144
- if @connopts.proxy && @connopts.proxy[:type] == :socks5
145
- socksify(client.req.uri.host, client.req.uri.port, *@connopts.proxy[:authorization]) { start }
146
- else
147
- start
148
- end
149
- end
150
-
151
- def start
152
- @conn.start_tls(@connopts.tls) if client && client.req.ssl?
153
- @conn.succeed
154
- end
155
-
156
- def redirect(client)
157
- @pending.push client
158
- end
159
-
160
- def unbind(reason)
161
- @clients.map { |c| c.unbind(reason) }
162
-
163
- if r = @pending.shift
164
- @clients.push r
165
-
166
- r.reset!
167
- @p.reset!
168
-
169
- begin
170
- @conn.set_deferred_status :unknown
171
- @conn.reconnect(r.req.host, r.req.port)
172
- @conn.callback { r.connection_completed }
173
- rescue EventMachine::ConnectionError => e
174
- @clients.pop.close(e.message)
175
- end
176
- end
177
- end
178
- alias :close :unbind
179
-
180
- def send_data(data)
181
- @conn.send_data data
182
- end
183
-
184
- def stream_file_data(filename, args = {})
185
- @conn.stream_file_data filename, args
186
- end
187
-
188
- private
189
-
190
- def client
191
- @clients.first
192
- end
193
- end
194
- end
1
+ module EventMachine
2
+
3
+ module HTTPMethods
4
+ def get options = {}, &blk; setup_request(:get, options, &blk); end
5
+ def head options = {}, &blk; setup_request(:head, options, &blk); end
6
+ def delete options = {}, &blk; setup_request(:delete,options, &blk); end
7
+ def put options = {}, &blk; setup_request(:put, options, &blk); end
8
+ def post options = {}, &blk; setup_request(:post, options, &blk); end
9
+ end
10
+
11
+ class HttpStubConnection < Connection
12
+ include Deferrable
13
+ attr_reader :parent
14
+
15
+ def parent=(p)
16
+ @parent = p
17
+ @parent.conn = self
18
+ end
19
+
20
+ def receive_data(data)
21
+ @parent.receive_data data
22
+ end
23
+
24
+ def connection_completed
25
+ @parent.connection_completed
26
+ end
27
+
28
+ def unbind(reason=nil)
29
+ @parent.unbind(reason)
30
+ end
31
+ end
32
+
33
+ class HttpConnection
34
+ include HTTPMethods
35
+ include Socksify
36
+
37
+ attr_reader :deferred
38
+ attr_accessor :error, :connopts, :uri, :conn
39
+
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.bind_connect(@connopts.bind, @connopts.bind_port, @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 exception,
68
+ # and return a failed deferred which fail any client request at next
69
+ # tick. We fail at next tick to keep a consistent API when the newly
70
+ # created HttpClient is failed. This approach has the advantage to
71
+ # remove a state check of @deferred_status after creating a new
72
+ # HttpRequest. The drawback is that users may setup a callback which we
73
+ # know won't be used.
74
+ #
75
+ # Once there is async-DNS, then we'll iterate over the outstanding
76
+ # client requests and fail them in order.
77
+ #
78
+ # Net outcome: failed connection will invoke the same ConnectionError
79
+ # message on the connection deferred, and on the client deferred.
80
+ #
81
+ EM.next_tick{client.close(e.message)}
82
+ end
83
+ end
84
+
85
+ def setup_request(method, options = {}, c = nil)
86
+ c ||= HttpClient.new(self, HttpClientOptions.new(@uri, options, method))
87
+ @deferred ? activate_connection(c) : finalize_request(c)
88
+ c
89
+ end
90
+
91
+ def finalize_request(c)
92
+ @conn.callback { c.connection_completed }
93
+
94
+ middleware.each do |m|
95
+ c.callback &m.method(:response) if m.respond_to?(:response)
96
+ end
97
+
98
+ @clients.push c
99
+ end
100
+
101
+ def middleware
102
+ [HttpRequest.middleware, @middleware].flatten
103
+ end
104
+
105
+ def post_init
106
+ @clients = []
107
+ @pending = []
108
+
109
+ @p = Http::Parser.new
110
+ @p.header_value_type = :mixed
111
+ @p.on_headers_complete = proc do |h|
112
+ client.parse_response_header(h, @p.http_version, @p.status_code)
113
+ :reset if client.req.no_body?
114
+ end
115
+
116
+ @p.on_body = proc do |b|
117
+ client.on_body_data(b)
118
+ end
119
+
120
+ @p.on_message_complete = proc do
121
+ if not client.continue?
122
+ c = @clients.shift
123
+ c.state = :finished
124
+ c.on_request_complete
125
+ end
126
+ end
127
+ end
128
+
129
+ def use(klass, *args, &block)
130
+ @middleware << klass.new(*args, &block)
131
+ end
132
+
133
+ def peer
134
+ Socket.unpack_sockaddr_in(@peer)[1] rescue nil
135
+ end
136
+
137
+ def receive_data(data)
138
+ begin
139
+ @p << data
140
+ rescue HTTP::Parser::Error => e
141
+ c = @clients.shift
142
+ c.nil? ? unbind(e.message) : c.on_error(e.message)
143
+ end
144
+ end
145
+
146
+ def connection_completed
147
+ @peer = @conn.get_peername
148
+
149
+ if @connopts.proxy && @connopts.proxy[:type] == :socks5
150
+ socksify(client.req.uri.host, client.req.uri.port, *@connopts.proxy[:authorization]) { start }
151
+ else
152
+ start
153
+ end
154
+ end
155
+
156
+ def start
157
+ @conn.start_tls(@connopts.tls) if client && client.req.ssl?
158
+ @conn.succeed
159
+ end
160
+
161
+ def redirect(client)
162
+ @pending.push client
163
+ end
164
+
165
+ def unbind(reason)
166
+ @clients.map { |c| c.unbind(reason) }
167
+
168
+ if r = @pending.shift
169
+ @clients.push r
170
+
171
+ r.reset!
172
+ @p.reset!
173
+
174
+ begin
175
+ @conn.set_deferred_status :unknown
176
+ @conn.reconnect(r.req.host, r.req.port)
177
+ @conn.callback { r.connection_completed }
178
+ rescue EventMachine::ConnectionError => e
179
+ @clients.pop.close(e.message)
180
+ end
181
+ end
182
+ end
183
+ alias :close :unbind
184
+
185
+ def send_data(data)
186
+ @conn.send_data data
187
+ end
188
+
189
+ def stream_file_data(filename, args = {})
190
+ @conn.stream_file_data filename, args
191
+ end
192
+
193
+ private
194
+
195
+ def client
196
+ @clients.first
197
+ end
198
+ end
199
+ end
@@ -1,5 +1,5 @@
1
1
  class HttpConnectionOptions
2
- attr_reader :host, :port, :tls, :proxy
2
+ attr_reader :host, :port, :tls, :proxy, :bind, :bind_port
3
3
  attr_reader :connect_timeout, :inactivity_timeout
4
4
 
5
5
  def initialize(uri, options)
@@ -9,6 +9,11 @@ class HttpConnectionOptions
9
9
  @tls = options[:tls] || options[:ssl] || {}
10
10
  @proxy = options[:proxy]
11
11
 
12
+ if bind = options[:bind]
13
+ @bind = bind[:host] || '0.0.0.0'
14
+ @bind_port = bind[:port] || 0
15
+ end
16
+
12
17
  uri = uri.kind_of?(Addressable::URI) ? uri : Addressable::URI::parse(uri.to_s)
13
18
  uri.port = (uri.scheme == "https" ? (uri.port || 443) : (uri.port || 80))
14
19
 
@@ -7,7 +7,7 @@ module EventMachine
7
7
  if defined?(EscapeUtils)
8
8
  EscapeUtils.escape_url(s.to_s)
9
9
  else
10
- s.to_s.gsub(/([^a-zA-Z0-9_.-]+)/n) {
10
+ s.to_s.gsub(/([^a-zA-Z0-9_.-]+)/) {
11
11
  '%'+$1.unpack('H2'*bytesize($1)).join('%').upcase
12
12
  }
13
13
  end
@@ -17,7 +17,7 @@ module EventMachine
17
17
  if defined?(EscapeUtils)
18
18
  EscapeUtils.unescape_url(s.to_s)
19
19
  else
20
- s.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/n) {
20
+ s.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/) {
21
21
  [$1.delete('%')].pack('H*')
22
22
  }
23
23
  end
@@ -52,5 +52,9 @@ module EventMachine
52
52
  def location
53
53
  self[HttpClient::LOCATION]
54
54
  end
55
+
56
+ def [](key)
57
+ super(key) || super(key.upcase.gsub('-','_'))
58
+ end
55
59
  end
56
60
  end
@@ -1,43 +1,57 @@
1
1
  module EventMachine
2
2
  module HttpStatus
3
3
  CODE = {
4
- 100 => 'Continue',
5
- 101 => 'Switching Protocols',
6
- 200 => 'OK',
7
- 201 => 'Created',
8
- 202 => 'Accepted',
9
- 203 => 'Non-Authoritative Information',
10
- 204 => 'No Content',
11
- 205 => 'Reset Content',
12
- 206 => 'Partial Content',
13
- 300 => 'Multiple Choices',
14
- 301 => 'Moved Permanently',
15
- 302 => 'Moved Temporarily',
16
- 303 => 'See Other',
17
- 304 => 'Not Modified',
18
- 305 => 'Use Proxy',
19
- 400 => 'Bad Request',
20
- 401 => 'Unauthorized',
21
- 402 => 'Payment Required',
22
- 403 => 'Forbidden',
23
- 404 => 'Not Found',
24
- 405 => 'Method Not Allowed',
25
- 406 => 'Not Acceptable',
26
- 407 => 'Proxy Authentication Required',
27
- 408 => 'Request Time-out',
28
- 409 => 'Conflict',
29
- 410 => 'Gone',
30
- 411 => 'Length Required',
31
- 412 => 'Precondition Failed',
32
- 413 => 'Request Entity Too Large',
33
- 414 => 'Request-URI Too Large',
34
- 415 => 'Unsupported Media Type',
35
- 500 => 'Internal Server Error',
36
- 501 => 'Not Implemented',
37
- 502 => 'Bad Gateway',
38
- 503 => 'Service Unavailable',
39
- 504 => 'Gateway Time-out',
40
- 505 => 'HTTP Version not supported'
4
+ 100 => 'Continue',
5
+ 101 => 'Switching Protocols',
6
+ 102 => 'Processing',
7
+ 200 => 'OK',
8
+ 201 => 'Created',
9
+ 202 => 'Accepted',
10
+ 203 => 'Non-Authoritative Information',
11
+ 204 => 'No Content',
12
+ 205 => 'Reset Content',
13
+ 206 => 'Partial Content',
14
+ 207 => 'Multi-Status',
15
+ 226 => 'IM Used',
16
+ 300 => 'Multiple Choices',
17
+ 301 => 'Moved Permanently',
18
+ 302 => 'Found',
19
+ 303 => 'See Other',
20
+ 304 => 'Not Modified',
21
+ 305 => 'Use Proxy',
22
+ 306 => 'Reserved',
23
+ 307 => 'Temporary Redirect',
24
+ 400 => 'Bad Request',
25
+ 401 => 'Unauthorized',
26
+ 402 => 'Payment Required',
27
+ 403 => 'Forbidden',
28
+ 404 => 'Not Found',
29
+ 405 => 'Method Not Allowed',
30
+ 406 => 'Not Acceptable',
31
+ 407 => 'Proxy Authentication Required',
32
+ 408 => 'Request Timeout',
33
+ 409 => 'Conflict',
34
+ 410 => 'Gone',
35
+ 411 => 'Length Required',
36
+ 412 => 'Precondition Failed',
37
+ 413 => 'Request Entity Too Large',
38
+ 414 => 'Request-URI Too Long',
39
+ 415 => 'Unsupported Media Type',
40
+ 416 => 'Requested Range Not Satisfiable',
41
+ 417 => 'Expectation Failed',
42
+ 422 => 'Unprocessable Entity',
43
+ 423 => 'Locked',
44
+ 424 => 'Failed Dependency',
45
+ 426 => 'Upgrade Required',
46
+ 500 => 'Internal Server Error',
47
+ 501 => 'Not Implemented',
48
+ 502 => 'Bad Gateway',
49
+ 503 => 'Service Unavailable',
50
+ 504 => 'Gateway Timeout',
51
+ 505 => 'HTTP Version Not Supported',
52
+ 506 => 'Variant Also Negotiates',
53
+ 507 => 'Insufficient Storage',
54
+ 510 => 'Not Extended'
41
55
  }
42
56
  end
43
57
  end