rev 0.2.0 → 0.2.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.
@@ -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