rev 0.2.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -38,6 +38,44 @@ static VALUE Rev_SSL_IO_start_ssl(VALUE self, int (*func)(), const char *funcnam
38
38
  static VALUE Rev_SSL_IO_read_nonblock(int argc, VALUE *argv, VALUE self);
39
39
  static VALUE Rev_SSL_IO_write_nonblock(VALUE self, VALUE str);
40
40
 
41
+ /*
42
+ * Time to monkey patch some C code!
43
+ */
44
+
45
+ /* Ruby 1.8 leaves us no recourse but to commonly couple to the OpenSSL native
46
+ extension through externs. Ugh */
47
+ #if RUBY_VERSION_CODE < 190
48
+
49
+ /* Externs from Ruby's OpenSSL native extension , in ossl_ssl.c*/
50
+ extern int ossl_ssl_ex_vcb_idx;
51
+ extern int ossl_ssl_ex_store_p;
52
+ extern int ossl_ssl_ex_ptr_idx;
53
+ extern int ossl_ssl_ex_client_cert_cb_idx;
54
+ extern int ossl_ssl_ex_tmp_dh_callback_idx;
55
+
56
+ /* #defines shamelessly copied and pasted from ossl_ssl.c */
57
+ #define ossl_ssl_get_io(o) rb_iv_get((o),"@io")
58
+ #define ossl_ssl_get_ctx(o) rb_iv_get((o),"@context")
59
+ #define ossl_sslctx_get_verify_cb(o) rb_iv_get((o),"@verify_callback")
60
+ #define ossl_sslctx_get_client_cert_cb(o) rb_iv_get((o),"@client_cert_cb")
61
+ #define ossl_sslctx_get_tmp_dh_cb(o) rb_iv_get((o),"@tmp_dh_callback")
62
+
63
+ #ifdef _WIN32
64
+ # define TO_SOCKET(s) _get_osfhandle(s)
65
+ #else
66
+ # define TO_SOCKET(s) s
67
+ #endif
68
+
69
+ #endif
70
+
71
+ #ifndef HAVE_RB_STR_SET_LEN
72
+ static void rb_str_set_len(VALUE str, long len)
73
+ {
74
+ RSTRING(str)->len = len;
75
+ RSTRING(str)->ptr[len] = '\0';
76
+ }
77
+ #endif
78
+
41
79
  void Init_rev_ssl()
42
80
  {
43
81
  rb_require("openssl");
@@ -63,6 +101,47 @@ void Init_rev_ssl()
63
101
  rb_define_method(cRev_SSL_IO, "write_nonblock", Rev_SSL_IO_write_nonblock, 1);
64
102
  }
65
103
 
104
+ #if RUBY_VERSION_CODE < 190
105
+ /* SSL initialization for Ruby 1.8 */
106
+ static VALUE
107
+ Rev_SSL_IO_ssl_setup(VALUE self)
108
+ {
109
+ VALUE io, v_ctx, cb;
110
+ SSL_CTX *ctx;
111
+ SSL *ssl;
112
+ OpenFile *fptr;
113
+
114
+ Data_Get_Struct(self, SSL, ssl);
115
+ if(!ssl) {
116
+ v_ctx = ossl_ssl_get_ctx(self);
117
+ Data_Get_Struct(v_ctx, SSL_CTX, ctx);
118
+
119
+ ssl = SSL_new(ctx);
120
+ if (!ssl) {
121
+ ossl_raise(eSSLError, "SSL_new:");
122
+ }
123
+ DATA_PTR(self) = ssl;
124
+
125
+ io = ossl_ssl_get_io(self);
126
+ GetOpenFile(io, fptr);
127
+ rb_io_check_readable(fptr);
128
+ rb_io_check_writable(fptr);
129
+ SSL_set_fd(ssl, TO_SOCKET(fileno(fptr->f)));
130
+ SSL_set_ex_data(ssl, ossl_ssl_ex_ptr_idx, (void*)self);
131
+ cb = ossl_sslctx_get_verify_cb(v_ctx);
132
+ SSL_set_ex_data(ssl, ossl_ssl_ex_vcb_idx, (void*)cb);
133
+ cb = ossl_sslctx_get_client_cert_cb(v_ctx);
134
+ SSL_set_ex_data(ssl, ossl_ssl_ex_client_cert_cb_idx, (void*)cb);
135
+ cb = ossl_sslctx_get_tmp_dh_cb(v_ctx);
136
+ SSL_set_ex_data(ssl, ossl_ssl_ex_tmp_dh_callback_idx, (void*)cb);
137
+ }
138
+
139
+ return Qtrue;
140
+ }
141
+ #endif
142
+
143
+ #if RUBY_VERSION_CODE >= 190
144
+ /* Slightly less insane SSL setup for Ruby 1.9 */
66
145
  static VALUE
67
146
  Rev_SSL_IO_ssl_setup(VALUE self)
68
147
  {
@@ -95,15 +174,13 @@ Rev_SSL_IO_ssl_setup(VALUE self)
95
174
  */
96
175
  rb_funcall(self, rb_intern("session="), 1, Qnil);
97
176
  }
177
+ #endif
98
178
 
99
179
  /* Ensure the error raised by calling #session= with a dummy argument is
100
180
  * the one we were expecting */
101
181
  static VALUE
102
182
  Rev_SSL_IO_ssl_setup_check(VALUE dummy, VALUE err)
103
183
  {
104
- if(!rb_obj_is_kind_of(err, rb_eTypeError))
105
- rb_raise(rb_eRuntimeError, "Rev::SSL not supported in this Ruby version, sorry");
106
-
107
184
  return Qnil;
108
185
  }
109
186
 
@@ -114,7 +191,12 @@ Rev_SSL_IO_ssl_setup_check(VALUE dummy, VALUE err)
114
191
  static VALUE
115
192
  Rev_SSL_IO_connect_nonblock(VALUE self)
116
193
  {
194
+ #if RUBY_VERSION_CODE >= 190
117
195
  rb_rescue(Rev_SSL_IO_ssl_setup, self, Rev_SSL_IO_ssl_setup_check, Qnil);
196
+ #else
197
+ Rev_SSL_IO_ssl_setup(self);
198
+ #endif
199
+
118
200
  return Rev_SSL_IO_start_ssl(self, SSL_connect, "SSL_connect");
119
201
  }
120
202
 
@@ -125,7 +207,12 @@ Rev_SSL_IO_connect_nonblock(VALUE self)
125
207
  static VALUE
126
208
  Rev_SSL_IO_accept_nonblock(VALUE self)
127
209
  {
210
+ #if RUBY_VERSION_CODE >= 190
128
211
  rb_rescue(Rev_SSL_IO_ssl_setup, self, 0, 0);
212
+ #else
213
+ Rev_SSL_IO_ssl_setup(self);
214
+ #endif
215
+
129
216
  return Rev_SSL_IO_start_ssl(self, SSL_accept, "SSL_accept");
130
217
  }
131
218
 
data/lib/rev.rb CHANGED
@@ -4,9 +4,15 @@
4
4
  # See file LICENSE for details
5
5
  #++
6
6
 
7
+ # Pull in the OpenSSL extension if available
8
+ begin
9
+ require 'openssl'
10
+ rescue LoadError
11
+ end
12
+
7
13
  require File.dirname(__FILE__) + '/rev_ext'
8
14
  require File.dirname(__FILE__) + '/rev/loop'
9
- require File.dirname(__FILE__) + '/rev/watcher'
15
+ require File.dirname(__FILE__) + '/rev/meta'
10
16
  require File.dirname(__FILE__) + '/rev/io_watcher'
11
17
  require File.dirname(__FILE__) + '/rev/timer_watcher'
12
18
  require File.dirname(__FILE__) + '/rev/async_watcher'
@@ -18,6 +24,6 @@ require File.dirname(__FILE__) + '/rev/server'
18
24
  require File.dirname(__FILE__) + '/rev/http_client'
19
25
 
20
26
  module Rev
21
- Rev::VERSION = '0.2.0' unless defined? Rev::VERSION
27
+ Rev::VERSION = '0.2.1' unless defined? Rev::VERSION
22
28
  def self.version() VERSION end
23
29
  end
@@ -68,7 +68,7 @@ module Rev
68
68
 
69
69
  # Map all header keys to a downcased string version
70
70
  def munge_header_keys(head)
71
- head.reduce({}) { |h, (k, v)| h[k.to_s.downcase] = v; h }
71
+ head.inject({}) { |h, (k, v)| h[k.to_s.downcase] = v; h }
72
72
  end
73
73
 
74
74
  # HTTP is kind of retarded that you have to specify
@@ -98,15 +98,15 @@ module Rev
98
98
  end
99
99
 
100
100
  def encode_headers(head)
101
- head.reduce('') do |result, (k, v)|
101
+ head.inject('') do |result, (key, value)|
102
102
  # Munge keys from foo-bar-baz to Foo-Bar-Baz
103
- k = k.split('-').map(&:capitalize).join('-')
104
- result << encode_field(k, v)
103
+ key = key.split('-').map { |k| k.capitalize }.join('-')
104
+ result << encode_field(key, value)
105
105
  end
106
106
  end
107
107
 
108
108
  def encode_cookies(cookies)
109
- cookies.reduce('') { |result, (k, v)| result << encode_field('Cookie', encode_param(k, v)) }
109
+ cookies.inject('') { |result, (k, v)| result << encode_field('Cookie', encode_param(k, v)) }
110
110
  end
111
111
  end
112
112
 
@@ -167,7 +167,7 @@ module Rev
167
167
  # Specify the request body (you must encode it for now)
168
168
  #
169
169
  def request(method, path, options = {})
170
- raise ArgumentError, "invalid request path" unless path[0] == '/'
170
+ raise ArgumentError, "invalid request path" unless /^\// === path
171
171
  raise RuntimeError, "request already sent" if @requested
172
172
 
173
173
  @method, @path, @options = method, path, options
@@ -13,16 +13,44 @@ module Rev
13
13
  # This class is primarily meant as a base class for other streams
14
14
  # which need non-blocking writing, and is used to implement Rev's
15
15
  # Socket class and its associated subclasses.
16
- class IO < IOWatcher
16
+ class IO
17
+ extend Meta
18
+
17
19
  # Maximum number of bytes to consume at once
18
20
  INPUT_SIZE = 16384
19
21
 
20
22
  def initialize(io)
21
- @io = io
22
- @write_buffer = Rev::Buffer.new
23
- super(@io)
23
+ @_io = io
24
+ @_write_buffer = Rev::Buffer.new
25
+ @_read_watcher = Watcher.new(io, self, :r)
26
+ @_write_watcher = Watcher.new(io, self, :w)
24
27
  end
25
28
 
29
+ #
30
+ # Watcher methods, delegated to @_read_watcher
31
+ #
32
+
33
+ # Attach to the event loop
34
+ def attach(loop); @_read_watcher.attach loop; self; end
35
+
36
+ # Detach from the event loop
37
+ def detach; @_read_watcher.detach; self; end
38
+
39
+ # Enable the watcher
40
+ def enable; @_read_watcher.enable; self; end
41
+
42
+ # Disable the watcher
43
+ def disable; @_read_watcher.disable; self; end
44
+
45
+ # Is the watcher attached?
46
+ def attached?; @_read_watcher.attached?; end
47
+
48
+ # Is the watcher enabled?
49
+ def enabled?; @_read_watcher.enabled?; end
50
+
51
+ # Obtain the event loop associated with this object
52
+ def evloop; @_read_watcher.evloop; end
53
+
26
54
  #
27
55
  # Callbacks for asynchronous events
28
56
  #
@@ -45,21 +73,21 @@ module Rev
45
73
 
46
74
  # Write data in a buffered, non-blocking manner
47
75
  def write(data)
48
- @write_buffer << data
76
+ @_write_buffer << data
49
77
  schedule_write
50
78
  data.size
51
79
  end
52
80
 
53
81
  # Number of bytes are currently in the output buffer
54
82
  def output_buffer_size
55
- @write_buffer.size
83
+ @_write_buffer.size
56
84
  end
57
85
 
58
86
  # Close the IO stream
59
87
  def close
60
88
  detach if attached?
61
89
  detach_write_watcher
62
- @io.close unless @io.closed?
90
+ @_io.close unless @_io.closed?
63
91
 
64
92
  on_close
65
93
  nil
@@ -67,7 +95,7 @@ module Rev
67
95
 
68
96
  # Is the IO object closed?
69
97
  def closed?
70
- @io.closed?
98
+ @_io.closed?
71
99
  end
72
100
 
73
101
  #########
@@ -77,7 +105,8 @@ module Rev
77
105
  # Read from the input buffer and dispatch to on_read
78
106
  def on_readable
79
107
  begin
80
- on_read @io.read_nonblock(INPUT_SIZE)
108
+ on_read @_io.read_nonblock(INPUT_SIZE)
109
+ rescue Errno::EAGAIN
81
110
  rescue Errno::ECONNRESET, EOFError
82
111
  close
83
112
  end
@@ -86,12 +115,12 @@ module Rev
86
115
  # Write the contents of the output buffer
87
116
  def on_writable
88
117
  begin
89
- @write_buffer.write_to(@io)
118
+ @_write_buffer.write_to(@_io)
90
119
  rescue Errno::EPIPE, Errno::ECONNRESET
91
120
  return close
92
121
  end
93
122
 
94
- if @write_buffer.empty?
123
+ if @_write_buffer.empty?
95
124
  disable_write_watcher
96
125
  on_write_complete
97
126
  end
@@ -105,37 +134,32 @@ module Rev
105
134
  end
106
135
  end
107
136
 
108
- # Return a handle to the writing IOWatcher
109
- def write_watcher
110
- @write_watcher ||= WriteWatcher.new(@io, self)
111
- end
112
-
113
137
  def enable_write_watcher
114
- if write_watcher.attached?
115
- write_watcher.enable unless write_watcher.enabled?
138
+ if @_write_watcher.attached?
139
+ @_write_watcher.enable unless @_write_watcher.enabled?
116
140
  else
117
- write_watcher.attach(evloop)
141
+ @_write_watcher.attach(evloop)
118
142
  end
119
143
  end
120
144
 
121
145
  def disable_write_watcher
122
- @write_watcher.disable if @write_watcher and @write_watcher.enabled?
146
+ @_write_watcher.disable if @_write_watcher and @_write_watcher.enabled?
123
147
  end
124
148
 
125
149
  def detach_write_watcher
126
- @write_watcher.detach if @write_watcher and @write_watcher.attached?
150
+ @_write_watcher.detach if @_write_watcher and @_write_watcher.attached?
127
151
  end
128
-
129
- class WriteWatcher < IOWatcher
130
- def initialize(ruby_io, rev_io)
152
+
153
+ # Internal class implementing watchers used by Rev::IO
154
+ class Watcher < IOWatcher
155
+ def initialize(ruby_io, rev_io, flags)
131
156
  @rev_io = rev_io
132
- super(ruby_io, :w)
157
+ super(ruby_io, flags)
133
158
  end
134
159
 
135
- # Delegate on_writable to the Rev::IO object
136
- def on_writable
137
- @rev_io.__send__(:on_writable)
138
- end
160
+ # Configure IOWatcher event callbacks to call the method passed to #initialize
161
+ def on_readable; @rev_io.__send__(:on_readable); end
162
+ def on_writable; @rev_io.__send__(:on_writable); end
139
163
  end
140
164
  end
141
165
  end
@@ -5,12 +5,13 @@
5
5
  #++
6
6
 
7
7
  module Rev
8
- class IOWatcher
8
+ class IOWatcher
9
9
  # The actual implementation of this class resides in the C extension
10
10
  # Here we metaprogram proper event_callbacks for the callback methods
11
11
  # These can take a block and store it to be called when the event
12
12
  # is actually fired.
13
13
 
14
+ extend Meta
14
15
  event_callback :on_readable, :on_writable
15
16
  end
16
17
  end
@@ -5,12 +5,12 @@
5
5
  #++
6
6
 
7
7
  module Rev
8
- class Watcher
8
+ module Meta
9
9
  # Use an alternate watcher with the attach/detach/enable/disable methods
10
10
  # if it is presently assigned. This is useful if you are waiting for
11
11
  # an event to occur before the current watcher can be used in earnest,
12
12
  # such as making an outgoing TCP connection.
13
- def self.watcher_delegate(proxy_var)
13
+ def watcher_delegate(proxy_var)
14
14
  %w{attach detach enable disable}.each do |method|
15
15
  module_eval <<-EOD
16
16
  def #{method}(*args)
@@ -29,7 +29,7 @@ module Rev
29
29
  # This is done by giving a block to the callback method, which is captured
30
30
  # as a proc and stored for later. If the method is called without a block,
31
31
  # the stored block is executed if present, otherwise it's a noop.
32
- def self.event_callback(*methods)
32
+ def event_callback(*methods)
33
33
  methods.each do |method|
34
34
  module_eval <<-EOD
35
35
  def #{method}(*args, &block)
@@ -11,8 +11,8 @@ module Rev
11
11
  # connections is a Socket, but any subclass of IOWatcher is acceptable.
12
12
  def initialize(listen_socket, klass = Socket, *args, &block)
13
13
  # Ensure the provided class responds to attach
14
- unless klass.allocate.is_a? IOWatcher
15
- raise ArgumentError, "provided class must descend from IOWatcher"
14
+ unless klass.allocate.is_a? IO
15
+ raise ArgumentError, "can't convert #{klass} to Rev::IO"
16
16
  end
17
17
 
18
18
  # Verify the arity of the provided arguments
@@ -10,19 +10,19 @@ require 'resolv'
10
10
  module Rev
11
11
  class Socket < IO
12
12
  def self.connect(socket, *args)
13
- new(socket, *args).instance_eval {
14
- @connector = Connector.new(self, socket)
13
+ new(socket, *args).instance_eval do
14
+ @_connector = Connector.new(self, socket)
15
15
  self
16
- }
16
+ end
17
17
  end
18
18
 
19
- watcher_delegate :@connector
19
+ watcher_delegate :@_connector
20
20
 
21
21
  def attach(evloop)
22
- raise RuntimeError, "connection failed" if @failed
22
+ raise RuntimeError, "connection failed" if @_failed
23
23
 
24
- if @connector
25
- @connector.attach(evloop)
24
+ if @_connector
25
+ @_connector.attach(evloop)
26
26
  return self
27
27
  end
28
28
 
@@ -56,11 +56,11 @@ module Rev
56
56
  detach
57
57
 
58
58
  if connect_successful?
59
- @rev_socket.instance_eval { @connector = nil }
59
+ @rev_socket.instance_eval { @_connector = nil }
60
60
  @rev_socket.attach(evl)
61
61
  @rev_socket.__send__(:on_connect)
62
62
  else
63
- @rev_socket.instance_eval { @failed = true }
63
+ @rev_socket.instance_eval { @_failed = true }
64
64
  @rev_socket.__send__(:on_connect_failed)
65
65
  end
66
66
  end
@@ -77,7 +77,7 @@ module Rev
77
77
 
78
78
  class TCPSocket < Socket
79
79
  attr_reader :remote_host, :remote_addr, :remote_port, :address_family
80
- watcher_delegate :@resolver
80
+ watcher_delegate :@_resolver
81
81
 
82
82
  # Similar to .new, but used in cases where the resulting object is in a
83
83
  # "half-open" state. This is primarily used for when asynchronous
@@ -114,7 +114,7 @@ module Rev
114
114
  # Called by precreate during asyncronous DNS resolution
115
115
  def preinitialize(addr, port, *args)
116
116
  @remote_host, @remote_addr, @remote_port = addr, addr, port
117
- @resolver = TCPConnectResolver.new(self, addr, port, *args)
117
+ @_resolver = TCPConnectResolver.new(self, addr, port, *args)
118
118
  end
119
119
 
120
120
  private :preinitialize
@@ -168,22 +168,28 @@ module Rev
168
168
  def on_success(addr)
169
169
  host, port, args = @host, @port, @args
170
170
 
171
- @sock.instance_eval {
172
- # DNSResolver only supports IPv4 so we can safely assume an IPv4 address
173
- socket = TCPConnectSocket.new(::Socket::AF_INET, addr, port, host)
171
+ @sock.instance_eval do
172
+ # DNSResolver only supports IPv4 so we can safely assume IPv4 address
173
+ begin
174
+ socket = TCPConnectSocket.new(::Socket::AF_INET, addr, port, host)
175
+ rescue Errno::ENETUNREACH
176
+ on_connect_failed
177
+ return
178
+ end
179
+
174
180
  initialize(socket, *args)
175
- @connector = Socket::Connector.new(self, socket)
176
- @resolver = nil
177
- }
181
+ @_connector = Socket::Connector.new(self, socket)
182
+ @_resolver = nil
183
+ end
178
184
  @sock.attach(evloop)
179
185
  end
180
186
 
181
187
  def on_failure
182
188
  @sock.__send__(:on_resolve_failed)
183
- @sock.instance_eval {
184
- @resolver = nil
185
- @failed = true
186
- }
189
+ @sock.instance_eval do
190
+ @_resolver = nil
191
+ @_failed = true
192
+ end
187
193
  return
188
194
  end
189
195
  end