em-http-request 1.0.0 → 1.0.1

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.

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