wj_eventmachine 1.3.0.dev.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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +179 -0
- data/GNU +281 -0
- data/LICENSE +60 -0
- data/README.md +110 -0
- data/docs/DocumentationGuidesIndex.md +27 -0
- data/docs/GettingStarted.md +520 -0
- data/docs/old/ChangeLog +211 -0
- data/docs/old/DEFERRABLES +246 -0
- data/docs/old/EPOLL +141 -0
- data/docs/old/INSTALL +13 -0
- data/docs/old/KEYBOARD +42 -0
- data/docs/old/LEGAL +25 -0
- data/docs/old/LIGHTWEIGHT_CONCURRENCY +130 -0
- data/docs/old/PURE_RUBY +75 -0
- data/docs/old/RELEASE_NOTES +94 -0
- data/docs/old/SMTP +4 -0
- data/docs/old/SPAWNED_PROCESSES +148 -0
- data/docs/old/TODO +8 -0
- data/examples/guides/getting_started/01_eventmachine_echo_server.rb +18 -0
- data/examples/guides/getting_started/02_eventmachine_echo_server_that_recognizes_exit_command.rb +22 -0
- data/examples/guides/getting_started/03_simple_chat_server.rb +149 -0
- data/examples/guides/getting_started/04_simple_chat_server_step_one.rb +27 -0
- data/examples/guides/getting_started/05_simple_chat_server_step_two.rb +43 -0
- data/examples/guides/getting_started/06_simple_chat_server_step_three.rb +98 -0
- data/examples/guides/getting_started/07_simple_chat_server_step_four.rb +121 -0
- data/examples/guides/getting_started/08_simple_chat_server_step_five.rb +141 -0
- data/examples/old/ex_channel.rb +43 -0
- data/examples/old/ex_queue.rb +2 -0
- data/examples/old/ex_tick_loop_array.rb +15 -0
- data/examples/old/ex_tick_loop_counter.rb +32 -0
- data/examples/old/helper.rb +2 -0
- data/ext/binder.cpp +124 -0
- data/ext/binder.h +52 -0
- data/ext/cmain.cpp +1046 -0
- data/ext/ed.cpp +2238 -0
- data/ext/ed.h +460 -0
- data/ext/em.cpp +2378 -0
- data/ext/em.h +266 -0
- data/ext/eventmachine.h +152 -0
- data/ext/extconf.rb +285 -0
- data/ext/fastfilereader/extconf.rb +120 -0
- data/ext/fastfilereader/mapper.cpp +214 -0
- data/ext/fastfilereader/mapper.h +59 -0
- data/ext/fastfilereader/rubymain.cpp +126 -0
- data/ext/kb.cpp +79 -0
- data/ext/page.cpp +107 -0
- data/ext/page.h +51 -0
- data/ext/pipe.cpp +354 -0
- data/ext/project.h +174 -0
- data/ext/rubymain.cpp +1610 -0
- data/ext/ssl.cpp +627 -0
- data/ext/ssl.h +103 -0
- data/ext/wait_for_single_fd.h +36 -0
- data/java/.classpath +8 -0
- data/java/.project +17 -0
- data/java/src/com/rubyeventmachine/EmReactor.java +625 -0
- data/java/src/com/rubyeventmachine/EmReactorException.java +40 -0
- data/java/src/com/rubyeventmachine/EmReactorInterface.java +70 -0
- data/java/src/com/rubyeventmachine/EventableChannel.java +72 -0
- data/java/src/com/rubyeventmachine/EventableDatagramChannel.java +201 -0
- data/java/src/com/rubyeventmachine/EventableSocketChannel.java +415 -0
- data/java/src/com/rubyeventmachine/NullEmReactor.java +157 -0
- data/java/src/com/rubyeventmachine/NullEventableChannel.java +81 -0
- data/lib/em/buftok.rb +59 -0
- data/lib/em/callback.rb +58 -0
- data/lib/em/channel.rb +69 -0
- data/lib/em/completion.rb +307 -0
- data/lib/em/connection.rb +776 -0
- data/lib/em/deferrable.rb +210 -0
- data/lib/em/deferrable/pool.rb +2 -0
- data/lib/em/file_watch.rb +73 -0
- data/lib/em/future.rb +61 -0
- data/lib/em/io_streamer.rb +68 -0
- data/lib/em/iterator.rb +252 -0
- data/lib/em/messages.rb +66 -0
- data/lib/em/pool.rb +151 -0
- data/lib/em/process_watch.rb +45 -0
- data/lib/em/processes.rb +123 -0
- data/lib/em/protocols.rb +37 -0
- data/lib/em/protocols/header_and_content.rb +138 -0
- data/lib/em/protocols/httpclient.rb +303 -0
- data/lib/em/protocols/httpclient2.rb +602 -0
- data/lib/em/protocols/line_and_text.rb +125 -0
- data/lib/em/protocols/line_protocol.rb +33 -0
- data/lib/em/protocols/linetext2.rb +179 -0
- data/lib/em/protocols/memcache.rb +331 -0
- data/lib/em/protocols/object_protocol.rb +46 -0
- data/lib/em/protocols/postgres3.rb +246 -0
- data/lib/em/protocols/saslauth.rb +175 -0
- data/lib/em/protocols/smtpclient.rb +394 -0
- data/lib/em/protocols/smtpserver.rb +666 -0
- data/lib/em/protocols/socks4.rb +66 -0
- data/lib/em/protocols/stomp.rb +205 -0
- data/lib/em/protocols/tcptest.rb +54 -0
- data/lib/em/pure_ruby.rb +1299 -0
- data/lib/em/queue.rb +80 -0
- data/lib/em/resolver.rb +232 -0
- data/lib/em/spawnable.rb +84 -0
- data/lib/em/streamer.rb +118 -0
- data/lib/em/threaded_resource.rb +90 -0
- data/lib/em/tick_loop.rb +85 -0
- data/lib/em/timers.rb +61 -0
- data/lib/em/version.rb +3 -0
- data/lib/eventmachine.rb +1602 -0
- data/lib/jeventmachine.rb +318 -0
- data/rakelib/package.rake +120 -0
- data/rakelib/test.rake +6 -0
- data/rakelib/test_pure.rake +11 -0
- data/tests/client.crt +31 -0
- data/tests/client.key +51 -0
- data/tests/dhparam.pem +13 -0
- data/tests/em_ssl_handlers.rb +153 -0
- data/tests/em_test_helper.rb +198 -0
- data/tests/jruby/test_jeventmachine.rb +38 -0
- data/tests/test_attach.rb +199 -0
- data/tests/test_basic.rb +321 -0
- data/tests/test_channel.rb +75 -0
- data/tests/test_completion.rb +178 -0
- data/tests/test_connection_count.rb +83 -0
- data/tests/test_connection_write.rb +35 -0
- data/tests/test_defer.rb +35 -0
- data/tests/test_deferrable.rb +35 -0
- data/tests/test_epoll.rb +141 -0
- data/tests/test_error_handler.rb +38 -0
- data/tests/test_exc.rb +37 -0
- data/tests/test_file_watch.rb +86 -0
- data/tests/test_fork.rb +75 -0
- data/tests/test_futures.rb +170 -0
- data/tests/test_handler_check.rb +35 -0
- data/tests/test_hc.rb +155 -0
- data/tests/test_httpclient.rb +238 -0
- data/tests/test_httpclient2.rb +132 -0
- data/tests/test_idle_connection.rb +31 -0
- data/tests/test_inactivity_timeout.rb +102 -0
- data/tests/test_io_streamer.rb +47 -0
- data/tests/test_ipv4.rb +96 -0
- data/tests/test_ipv6.rb +107 -0
- data/tests/test_iterator.rb +122 -0
- data/tests/test_kb.rb +28 -0
- data/tests/test_keepalive.rb +113 -0
- data/tests/test_line_protocol.rb +33 -0
- data/tests/test_ltp.rb +155 -0
- data/tests/test_ltp2.rb +332 -0
- data/tests/test_many_fds.rb +21 -0
- data/tests/test_next_tick.rb +104 -0
- data/tests/test_object_protocol.rb +36 -0
- data/tests/test_pause.rb +109 -0
- data/tests/test_pending_connect_timeout.rb +52 -0
- data/tests/test_pool.rb +196 -0
- data/tests/test_process_watch.rb +50 -0
- data/tests/test_processes.rb +128 -0
- data/tests/test_proxy_connection.rb +180 -0
- data/tests/test_pure.rb +156 -0
- data/tests/test_queue.rb +64 -0
- data/tests/test_resolver.rb +129 -0
- data/tests/test_running.rb +14 -0
- data/tests/test_sasl.rb +46 -0
- data/tests/test_send_file.rb +217 -0
- data/tests/test_servers.rb +32 -0
- data/tests/test_shutdown_hooks.rb +23 -0
- data/tests/test_smtpclient.rb +75 -0
- data/tests/test_smtpserver.rb +90 -0
- data/tests/test_sock_opt.rb +53 -0
- data/tests/test_spawn.rb +290 -0
- data/tests/test_ssl_args.rb +41 -0
- data/tests/test_ssl_dhparam.rb +57 -0
- data/tests/test_ssl_ecdh_curve.rb +57 -0
- data/tests/test_ssl_extensions.rb +24 -0
- data/tests/test_ssl_methods.rb +31 -0
- data/tests/test_ssl_protocols.rb +190 -0
- data/tests/test_ssl_verify.rb +52 -0
- data/tests/test_stomp.rb +38 -0
- data/tests/test_system.rb +46 -0
- data/tests/test_threaded_resource.rb +68 -0
- data/tests/test_tick_loop.rb +58 -0
- data/tests/test_timers.rb +150 -0
- data/tests/test_ud.rb +8 -0
- data/tests/test_unbind_reason.rb +40 -0
- metadata +384 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
module EventMachine
|
|
2
|
+
module Protocols
|
|
3
|
+
# Basic SOCKS v4 client implementation
|
|
4
|
+
#
|
|
5
|
+
# Use as you would any regular connection:
|
|
6
|
+
#
|
|
7
|
+
# class MyConn < EM::P::Socks4
|
|
8
|
+
# def post_init
|
|
9
|
+
# send_data("sup")
|
|
10
|
+
# end
|
|
11
|
+
#
|
|
12
|
+
# def receive_data(data)
|
|
13
|
+
# send_data("you said: #{data}")
|
|
14
|
+
# end
|
|
15
|
+
# end
|
|
16
|
+
#
|
|
17
|
+
# EM.connect socks_host, socks_port, MyConn, host, port
|
|
18
|
+
#
|
|
19
|
+
class Socks4 < Connection
|
|
20
|
+
def initialize(host, port)
|
|
21
|
+
@host = Socket.gethostbyname(host).last
|
|
22
|
+
@port = port
|
|
23
|
+
@socks_error_code = nil
|
|
24
|
+
@buffer = ''
|
|
25
|
+
setup_methods
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def setup_methods
|
|
29
|
+
class << self
|
|
30
|
+
def post_init; socks_post_init; end
|
|
31
|
+
def receive_data(*a); socks_receive_data(*a); end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def restore_methods
|
|
36
|
+
class << self
|
|
37
|
+
remove_method :post_init
|
|
38
|
+
remove_method :receive_data
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def socks_post_init
|
|
43
|
+
header = [4, 1, @port, @host, 0].flatten.pack("CCnA4C")
|
|
44
|
+
send_data(header)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def socks_receive_data(data)
|
|
48
|
+
@buffer << data
|
|
49
|
+
return if @buffer.size < 8
|
|
50
|
+
|
|
51
|
+
header_resp = @buffer.slice! 0, 8
|
|
52
|
+
_, r = header_resp.unpack("cc")
|
|
53
|
+
if r != 90
|
|
54
|
+
@socks_error_code = r
|
|
55
|
+
close_connection
|
|
56
|
+
return
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
restore_methods
|
|
60
|
+
|
|
61
|
+
post_init
|
|
62
|
+
receive_data(@buffer) unless @buffer.empty?
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
#--
|
|
2
|
+
#
|
|
3
|
+
# Author:: Francis Cianfrocca (gmail: blackhedd)
|
|
4
|
+
# Homepage:: http://rubyeventmachine.com
|
|
5
|
+
# Date:: 15 November 2006
|
|
6
|
+
#
|
|
7
|
+
# See EventMachine and EventMachine::Connection for documentation and
|
|
8
|
+
# usage examples.
|
|
9
|
+
#
|
|
10
|
+
#----------------------------------------------------------------------------
|
|
11
|
+
#
|
|
12
|
+
# Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved.
|
|
13
|
+
# Gmail: blackhedd
|
|
14
|
+
#
|
|
15
|
+
# This program is free software; you can redistribute it and/or modify
|
|
16
|
+
# it under the terms of either: 1) the GNU General Public License
|
|
17
|
+
# as published by the Free Software Foundation; either version 2 of the
|
|
18
|
+
# License, or (at your option) any later version; or 2) Ruby's License.
|
|
19
|
+
#
|
|
20
|
+
# See the file COPYING for complete licensing information.
|
|
21
|
+
#
|
|
22
|
+
#---------------------------------------------------------------------------
|
|
23
|
+
#
|
|
24
|
+
#
|
|
25
|
+
#
|
|
26
|
+
|
|
27
|
+
module EventMachine
|
|
28
|
+
module Protocols
|
|
29
|
+
|
|
30
|
+
# Implements Stomp (http://docs.codehaus.org/display/STOMP/Protocol).
|
|
31
|
+
#
|
|
32
|
+
# == Usage example
|
|
33
|
+
#
|
|
34
|
+
# module StompClient
|
|
35
|
+
# include EM::Protocols::Stomp
|
|
36
|
+
#
|
|
37
|
+
# def connection_completed
|
|
38
|
+
# connect :login => 'guest', :passcode => 'guest'
|
|
39
|
+
# end
|
|
40
|
+
#
|
|
41
|
+
# def receive_msg msg
|
|
42
|
+
# if msg.command == "CONNECTED"
|
|
43
|
+
# subscribe '/some/topic'
|
|
44
|
+
# else
|
|
45
|
+
# p ['got a message', msg]
|
|
46
|
+
# puts msg.body
|
|
47
|
+
# end
|
|
48
|
+
# end
|
|
49
|
+
# end
|
|
50
|
+
#
|
|
51
|
+
# EM.run{
|
|
52
|
+
# EM.connect 'localhost', 61613, StompClient
|
|
53
|
+
# }
|
|
54
|
+
#
|
|
55
|
+
module Stomp
|
|
56
|
+
include LineText2
|
|
57
|
+
|
|
58
|
+
class Message
|
|
59
|
+
# The command associated with the message, usually 'CONNECTED' or 'MESSAGE'
|
|
60
|
+
attr_accessor :command
|
|
61
|
+
# Hash containing headers such as destination and message-id
|
|
62
|
+
attr_accessor :header
|
|
63
|
+
alias :headers :header
|
|
64
|
+
# Body of the message
|
|
65
|
+
attr_accessor :body
|
|
66
|
+
|
|
67
|
+
# @private
|
|
68
|
+
def initialize
|
|
69
|
+
@header = {}
|
|
70
|
+
@state = :precommand
|
|
71
|
+
@content_length = nil
|
|
72
|
+
end
|
|
73
|
+
# @private
|
|
74
|
+
def consume_line line
|
|
75
|
+
if @state == :precommand
|
|
76
|
+
unless line =~ /\A\s*\Z/
|
|
77
|
+
@command = line
|
|
78
|
+
@state = :headers
|
|
79
|
+
end
|
|
80
|
+
elsif @state == :headers
|
|
81
|
+
if line == ""
|
|
82
|
+
if @content_length
|
|
83
|
+
yield( [:sized_text, @content_length+1] )
|
|
84
|
+
else
|
|
85
|
+
@state = :body
|
|
86
|
+
yield( [:unsized_text] )
|
|
87
|
+
end
|
|
88
|
+
elsif line =~ /\A([^:]+):(.+)\Z/
|
|
89
|
+
k = $1.dup.strip
|
|
90
|
+
v = $2.dup.strip
|
|
91
|
+
@header[k] = v
|
|
92
|
+
if k == "content-length"
|
|
93
|
+
@content_length = v.to_i
|
|
94
|
+
end
|
|
95
|
+
else
|
|
96
|
+
# This is a protocol error. How to signal it?
|
|
97
|
+
end
|
|
98
|
+
elsif @state == :body
|
|
99
|
+
@body = line
|
|
100
|
+
yield( [:dispatch] )
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# @private
|
|
106
|
+
def send_frame verb, headers={}, body=""
|
|
107
|
+
body = body.to_s
|
|
108
|
+
ary = [verb, "\n"]
|
|
109
|
+
body_bytesize = body.bytesize if body.respond_to? :bytesize
|
|
110
|
+
body_bytesize ||= body.size
|
|
111
|
+
headers.each {|k,v| ary << "#{k}:#{v}\n" }
|
|
112
|
+
ary << "content-length: #{body_bytesize}\n"
|
|
113
|
+
ary << "content-type: text/plain; charset=UTF-8\n" unless headers.has_key? 'content-type'
|
|
114
|
+
ary << "\n"
|
|
115
|
+
ary << body
|
|
116
|
+
ary << "\0"
|
|
117
|
+
send_data ary.join
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# @private
|
|
121
|
+
def receive_line line
|
|
122
|
+
@stomp_initialized || init_message_reader
|
|
123
|
+
@stomp_message.consume_line(line) {|outcome|
|
|
124
|
+
if outcome.first == :sized_text
|
|
125
|
+
set_text_mode outcome[1]
|
|
126
|
+
elsif outcome.first == :unsized_text
|
|
127
|
+
set_delimiter "\0"
|
|
128
|
+
elsif outcome.first == :dispatch
|
|
129
|
+
receive_msg(@stomp_message) if respond_to?(:receive_msg)
|
|
130
|
+
init_message_reader
|
|
131
|
+
end
|
|
132
|
+
}
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# @private
|
|
136
|
+
def receive_binary_data data
|
|
137
|
+
@stomp_message.body = data[0..-2]
|
|
138
|
+
receive_msg(@stomp_message) if respond_to?(:receive_msg)
|
|
139
|
+
init_message_reader
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# @private
|
|
143
|
+
def init_message_reader
|
|
144
|
+
@stomp_initialized = true
|
|
145
|
+
set_delimiter "\n"
|
|
146
|
+
set_line_mode
|
|
147
|
+
@stomp_message = Message.new
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# Invoked with an incoming Stomp::Message received from the STOMP server
|
|
151
|
+
def receive_msg msg
|
|
152
|
+
# stub, overwrite this in your handler
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# CONNECT command, for authentication
|
|
156
|
+
#
|
|
157
|
+
# connect :login => 'guest', :passcode => 'guest'
|
|
158
|
+
#
|
|
159
|
+
def connect parms={}
|
|
160
|
+
send_frame "CONNECT", parms
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# SEND command, for publishing messages to a topic
|
|
164
|
+
#
|
|
165
|
+
# send '/topic/name', 'some message here'
|
|
166
|
+
#
|
|
167
|
+
def send destination, body, parms={}
|
|
168
|
+
send_frame "SEND", parms.merge( :destination=>destination ), body.to_s
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# SUBSCRIBE command, for subscribing to topics
|
|
172
|
+
#
|
|
173
|
+
# subscribe '/topic/name', false
|
|
174
|
+
#
|
|
175
|
+
def subscribe dest, ack=false
|
|
176
|
+
send_frame "SUBSCRIBE", {:destination=>dest, :ack=>(ack ? "client" : "auto")}
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# ACK command, for acknowledging receipt of messages
|
|
180
|
+
#
|
|
181
|
+
# module StompClient
|
|
182
|
+
# include EM::P::Stomp
|
|
183
|
+
#
|
|
184
|
+
# def connection_completed
|
|
185
|
+
# connect :login => 'guest', :passcode => 'guest'
|
|
186
|
+
# # subscribe with ack mode
|
|
187
|
+
# subscribe '/some/topic', true
|
|
188
|
+
# end
|
|
189
|
+
#
|
|
190
|
+
# def receive_msg msg
|
|
191
|
+
# if msg.command == "MESSAGE"
|
|
192
|
+
# ack msg.headers['message-id']
|
|
193
|
+
# puts msg.body
|
|
194
|
+
# end
|
|
195
|
+
# end
|
|
196
|
+
# end
|
|
197
|
+
#
|
|
198
|
+
def ack msgid
|
|
199
|
+
send_frame "ACK", 'message-id'=> msgid
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
#--
|
|
2
|
+
#
|
|
3
|
+
# Author:: Francis Cianfrocca (gmail: blackhedd)
|
|
4
|
+
# Homepage:: http://rubyeventmachine.com
|
|
5
|
+
# Date:: 16 July 2006
|
|
6
|
+
#
|
|
7
|
+
# See EventMachine and EventMachine::Connection for documentation and
|
|
8
|
+
# usage examples.
|
|
9
|
+
#
|
|
10
|
+
#----------------------------------------------------------------------------
|
|
11
|
+
#
|
|
12
|
+
# Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved.
|
|
13
|
+
# Gmail: blackhedd
|
|
14
|
+
#
|
|
15
|
+
# This program is free software; you can redistribute it and/or modify
|
|
16
|
+
# it under the terms of either: 1) the GNU General Public License
|
|
17
|
+
# as published by the Free Software Foundation; either version 2 of the
|
|
18
|
+
# License, or (at your option) any later version; or 2) Ruby's License.
|
|
19
|
+
#
|
|
20
|
+
# See the file COPYING for complete licensing information.
|
|
21
|
+
#
|
|
22
|
+
#---------------------------------------------------------------------------
|
|
23
|
+
#
|
|
24
|
+
#
|
|
25
|
+
#
|
|
26
|
+
|
|
27
|
+
module EventMachine
|
|
28
|
+
module Protocols
|
|
29
|
+
|
|
30
|
+
# @private
|
|
31
|
+
class TcpConnectTester < Connection
|
|
32
|
+
include EventMachine::Deferrable
|
|
33
|
+
|
|
34
|
+
def self.test( host, port )
|
|
35
|
+
EventMachine.connect( host, port, self )
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def post_init
|
|
39
|
+
@start_time = Time.now
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def connection_completed
|
|
43
|
+
@completed = true
|
|
44
|
+
set_deferred_status :succeeded, (Time.now - @start_time)
|
|
45
|
+
close_connection
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def unbind
|
|
49
|
+
set_deferred_status :failed, (Time.now - @start_time) unless @completed
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
end
|
|
54
|
+
end
|
data/lib/em/pure_ruby.rb
ADDED
|
@@ -0,0 +1,1299 @@
|
|
|
1
|
+
#--
|
|
2
|
+
#
|
|
3
|
+
# Author:: Francis Cianfrocca (gmail: blackhedd)
|
|
4
|
+
# Homepage:: http://rubyeventmachine.com
|
|
5
|
+
# Date:: 8 Apr 2006
|
|
6
|
+
#
|
|
7
|
+
# See EventMachine and EventMachine::Connection for documentation and
|
|
8
|
+
# usage examples.
|
|
9
|
+
#
|
|
10
|
+
#----------------------------------------------------------------------------
|
|
11
|
+
#
|
|
12
|
+
# Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved.
|
|
13
|
+
# Gmail: blackhedd
|
|
14
|
+
#
|
|
15
|
+
# This program is free software; you can redistribute it and/or modify
|
|
16
|
+
# it under the terms of either: 1) the GNU General Public License
|
|
17
|
+
# as published by the Free Software Foundation; either version 2 of the
|
|
18
|
+
# License, or (at your option) any later version; or 2) Ruby's License.
|
|
19
|
+
#
|
|
20
|
+
# See the file COPYING for complete licensing information.
|
|
21
|
+
#
|
|
22
|
+
#-------------------------------------------------------------------
|
|
23
|
+
#
|
|
24
|
+
#
|
|
25
|
+
|
|
26
|
+
# TODO List:
|
|
27
|
+
# TCP-connects currently assume non-blocking connect is available- need to
|
|
28
|
+
# degrade automatically on versions of Ruby prior to June 2006.
|
|
29
|
+
#
|
|
30
|
+
|
|
31
|
+
require 'singleton'
|
|
32
|
+
require 'forwardable'
|
|
33
|
+
require 'socket'
|
|
34
|
+
require 'fcntl'
|
|
35
|
+
require 'set'
|
|
36
|
+
require 'openssl'
|
|
37
|
+
|
|
38
|
+
module EventMachine
|
|
39
|
+
# @private
|
|
40
|
+
class Error < Exception; end
|
|
41
|
+
# @private
|
|
42
|
+
class UnknownTimerFired < RuntimeError; end
|
|
43
|
+
# @private
|
|
44
|
+
class Unsupported < RuntimeError; end
|
|
45
|
+
# @private
|
|
46
|
+
class ConnectionError < RuntimeError; end
|
|
47
|
+
# @private
|
|
48
|
+
class ConnectionNotBound < RuntimeError; end
|
|
49
|
+
|
|
50
|
+
# Older versions of Ruby may not provide the SSLErrorWaitReadable
|
|
51
|
+
# OpenSSL class. Create an error class to act as a "proxy".
|
|
52
|
+
if defined?(OpenSSL::SSL::SSLErrorWaitReadable)
|
|
53
|
+
SSLConnectionWaitReadable = OpenSSL::SSL::SSLErrorWaitReadable
|
|
54
|
+
else
|
|
55
|
+
SSLConnectionWaitReadable = IO::WaitReadable
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Older versions of Ruby may not provide the SSLErrorWaitWritable
|
|
59
|
+
# OpenSSL class. Create an error class to act as a "proxy".
|
|
60
|
+
if defined?(OpenSSL::SSL::SSLErrorWaitWritable)
|
|
61
|
+
SSLConnectionWaitWritable = OpenSSL::SSL::SSLErrorWaitWritable
|
|
62
|
+
else
|
|
63
|
+
SSLConnectionWaitWritable = IO::WaitWritable
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
module EventMachine
|
|
68
|
+
class CertificateCreator
|
|
69
|
+
attr_reader :cert, :key
|
|
70
|
+
|
|
71
|
+
def initialize
|
|
72
|
+
@key = OpenSSL::PKey::RSA.new(1024)
|
|
73
|
+
public_key = @key.public_key
|
|
74
|
+
subject = "/C=EventMachine/O=EventMachine/OU=EventMachine/CN=EventMachine"
|
|
75
|
+
@cert = OpenSSL::X509::Certificate.new
|
|
76
|
+
@cert.subject = @cert.issuer = OpenSSL::X509::Name.parse(subject)
|
|
77
|
+
@cert.not_before = Time.now
|
|
78
|
+
@cert.not_after = Time.now + 365 * 24 * 60 * 60
|
|
79
|
+
@cert.public_key = public_key
|
|
80
|
+
@cert.serial = 0x0
|
|
81
|
+
@cert.version = 2
|
|
82
|
+
factory = OpenSSL::X509::ExtensionFactory.new
|
|
83
|
+
factory.subject_certificate = @cert
|
|
84
|
+
factory.issuer_certificate = @cert
|
|
85
|
+
@cert.extensions = [
|
|
86
|
+
factory.create_extension("basicConstraints","CA:TRUE", true),
|
|
87
|
+
factory.create_extension("subjectKeyIdentifier", "hash")
|
|
88
|
+
]
|
|
89
|
+
@cert.add_extension factory.create_extension("authorityKeyIdentifier", "keyid:always,issuer:always")
|
|
90
|
+
@cert.sign(@key, OpenSSL::Digest::SHA1.new)
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# @private
|
|
95
|
+
DefaultCertificate = CertificateCreator.new
|
|
96
|
+
|
|
97
|
+
# @private
|
|
98
|
+
DefaultDHKey1024 = OpenSSL::PKey::DH.new <<-_end_of_pem_
|
|
99
|
+
-----BEGIN DH PARAMETERS-----
|
|
100
|
+
MIGHAoGBAJ0lOVy0VIr/JebWn0zDwY2h+rqITFOpdNr6ugsgvkDXuucdcChhYExJ
|
|
101
|
+
AV/ZD2AWPbrTqV76mGRgJg4EddgT1zG0jq3rnFdMj2XzkBYx3BVvfR0Arnby0RHR
|
|
102
|
+
T4h7KZ/2zmjvV+eF8kBUHBJAojUlzxKj4QeO2x20FP9X5xmNUXeDAgEC
|
|
103
|
+
-----END DH PARAMETERS-----
|
|
104
|
+
_end_of_pem_
|
|
105
|
+
|
|
106
|
+
# @private
|
|
107
|
+
DefaultDHKey2048 = OpenSSL::PKey::DH.new <<-_end_of_pem_
|
|
108
|
+
-----BEGIN DH PARAMETERS-----
|
|
109
|
+
MIIBCAKCAQEA7E6kBrYiyvmKAMzQ7i8WvwVk9Y/+f8S7sCTN712KkK3cqd1jhJDY
|
|
110
|
+
JbrYeNV3kUIKhPxWHhObHKpD1R84UpL+s2b55+iMd6GmL7OYmNIT/FccKhTcveab
|
|
111
|
+
VBmZT86BZKYyf45hUF9FOuUM9xPzuK3Vd8oJQvfYMCd7LPC0taAEljQLR4Edf8E6
|
|
112
|
+
YoaOffgTf5qxiwkjnlVZQc3whgnEt9FpVMvQ9eknyeGB5KHfayAc3+hUAvI3/Cr3
|
|
113
|
+
1bNveX5wInh5GDx1FGhKBZ+s1H+aedudCm7sCgRwv8lKWYGiHzObSma8A86KG+MD
|
|
114
|
+
7Lo5JquQ3DlBodj3IDyPrxIv96lvRPFtAwIBAg==
|
|
115
|
+
-----END DH PARAMETERS-----
|
|
116
|
+
_end_of_pem_
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# @private
|
|
120
|
+
module EventMachine
|
|
121
|
+
class << self
|
|
122
|
+
# This is mostly useful for automated tests.
|
|
123
|
+
# Return a distinctive symbol so the caller knows whether he's dealing
|
|
124
|
+
# with an extension or with a pure-Ruby library.
|
|
125
|
+
# @private
|
|
126
|
+
def library_type
|
|
127
|
+
:pure_ruby
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# @private
|
|
131
|
+
def initialize_event_machine
|
|
132
|
+
Reactor.instance.initialize_for_run
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Changed 04Oct06: intervals from the caller are now in milliseconds, but our native-ruby
|
|
136
|
+
# processor still wants them in seconds.
|
|
137
|
+
# @private
|
|
138
|
+
def add_oneshot_timer interval
|
|
139
|
+
Reactor.instance.install_oneshot_timer(interval.to_f / 1000)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# @private
|
|
143
|
+
def run_machine
|
|
144
|
+
Reactor.instance.run
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# @private
|
|
148
|
+
def release_machine
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def stopping?
|
|
153
|
+
return Reactor.instance.stop_scheduled
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# @private
|
|
157
|
+
def stop
|
|
158
|
+
Reactor.instance.stop
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# @private
|
|
162
|
+
def connect_server host, port
|
|
163
|
+
bind_connect_server nil, nil, host, port
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# @private
|
|
167
|
+
def bind_connect_server bind_addr, bind_port, host, port
|
|
168
|
+
EvmaTCPClient.connect(bind_addr, bind_port, host, port).uuid
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# @private
|
|
172
|
+
def send_data target, data, datalength
|
|
173
|
+
selectable = Reactor.instance.get_selectable( target ) or raise "unknown send_data target"
|
|
174
|
+
selectable.send_data data
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# @private
|
|
178
|
+
def close_connection target, after_writing
|
|
179
|
+
selectable = Reactor.instance.get_selectable( target )
|
|
180
|
+
selectable.schedule_close after_writing if selectable
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# @private
|
|
184
|
+
def start_tcp_server host, port
|
|
185
|
+
(s = EvmaTCPServer.start_server host, port) or raise "no acceptor"
|
|
186
|
+
s.uuid
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# @private
|
|
190
|
+
def stop_tcp_server sig
|
|
191
|
+
#exit(1)
|
|
192
|
+
s = Reactor.instance.get_selectable(sig)
|
|
193
|
+
#puts "is there an instance :#{!s.blank?}"
|
|
194
|
+
unless s.nil?
|
|
195
|
+
s.schedule_close
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# @private
|
|
200
|
+
def start_unix_server chain
|
|
201
|
+
(s = EvmaUNIXServer.start_server chain) or raise "no acceptor"
|
|
202
|
+
s.uuid
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# @private
|
|
206
|
+
def connect_unix_server chain
|
|
207
|
+
EvmaUNIXClient.connect(chain).uuid
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
# @private
|
|
211
|
+
def signal_loopbreak
|
|
212
|
+
Reactor.instance.signal_loopbreak
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# @private
|
|
216
|
+
def get_peername sig
|
|
217
|
+
selectable = Reactor.instance.get_selectable( sig ) or raise "unknown get_peername target"
|
|
218
|
+
selectable.get_peername
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# @private
|
|
222
|
+
def get_sockname sig
|
|
223
|
+
selectable = Reactor.instance.get_selectable( sig ) or raise "unknown get_sockname target"
|
|
224
|
+
selectable.get_sockname
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
# @private
|
|
228
|
+
def open_udp_socket host, port
|
|
229
|
+
EvmaUDPSocket.create(host, port).uuid
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
# This is currently only for UDP!
|
|
233
|
+
# We need to make it work with unix-domain sockets as well.
|
|
234
|
+
# @private
|
|
235
|
+
def send_datagram target, data, datalength, host, port
|
|
236
|
+
selectable = Reactor.instance.get_selectable( target ) or raise "unknown send_data target"
|
|
237
|
+
selectable.send_datagram data, Socket::pack_sockaddr_in(port, host)
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
# Sets reactor quantum in milliseconds. The underlying Reactor function wants a (possibly
|
|
242
|
+
# fractional) number of seconds.
|
|
243
|
+
# @private
|
|
244
|
+
def set_timer_quantum interval
|
|
245
|
+
Reactor.instance.set_timer_quantum(( 1.0 * interval) / 1000.0)
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
# This method is a harmless no-op in the pure-Ruby implementation. This is intended to ensure
|
|
249
|
+
# that user code behaves properly across different EM implementations.
|
|
250
|
+
# @private
|
|
251
|
+
def epoll
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
# @private
|
|
255
|
+
def ssl?
|
|
256
|
+
true
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
def tls_parm_set?(parm)
|
|
260
|
+
!(parm.nil? || parm.empty?)
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
# This method takes a series of positional arguments for specifying such
|
|
264
|
+
# things as private keys and certificate chains. It's expected that the
|
|
265
|
+
# parameter list will grow as we add more supported features. ALL of these
|
|
266
|
+
# parameters are optional, and can be specified as empty or nil strings.
|
|
267
|
+
# @private
|
|
268
|
+
def set_tls_parms signature, priv_key, cert_chain, verify_peer, fail_if_no_peer_cert, sni_hostname, cipher_list, ecdh_curve, dhparam, protocols_bitmask
|
|
269
|
+
bitmask = protocols_bitmask
|
|
270
|
+
ssl_options = OpenSSL::SSL::OP_ALL
|
|
271
|
+
ssl_options |= OpenSSL::SSL::OP_NO_SSLv2 if defined?(OpenSSL::SSL::OP_NO_SSLv2) && EM_PROTO_SSLv2 & bitmask == 0
|
|
272
|
+
ssl_options |= OpenSSL::SSL::OP_NO_SSLv3 if defined?(OpenSSL::SSL::OP_NO_SSLv3) && EM_PROTO_SSLv3 & bitmask == 0
|
|
273
|
+
ssl_options |= OpenSSL::SSL::OP_NO_TLSv1 if defined?(OpenSSL::SSL::OP_NO_TLSv1) && EM_PROTO_TLSv1 & bitmask == 0
|
|
274
|
+
ssl_options |= OpenSSL::SSL::OP_NO_TLSv1_1 if defined?(OpenSSL::SSL::OP_NO_TLSv1_1) && EM_PROTO_TLSv1_1 & bitmask == 0
|
|
275
|
+
ssl_options |= OpenSSL::SSL::OP_NO_TLSv1_2 if defined?(OpenSSL::SSL::OP_NO_TLSv1_2) && EM_PROTO_TLSv1_2 & bitmask == 0
|
|
276
|
+
@tls_parms ||= {}
|
|
277
|
+
@tls_parms[signature] = {
|
|
278
|
+
:verify_peer => verify_peer,
|
|
279
|
+
:fail_if_no_peer_cert => fail_if_no_peer_cert,
|
|
280
|
+
:ssl_options => ssl_options
|
|
281
|
+
}
|
|
282
|
+
@tls_parms[signature][:priv_key] = File.read(priv_key) if tls_parm_set?(priv_key)
|
|
283
|
+
@tls_parms[signature][:cert_chain] = File.read(cert_chain) if tls_parm_set?(cert_chain)
|
|
284
|
+
@tls_parms[signature][:sni_hostname] = sni_hostname if tls_parm_set?(sni_hostname)
|
|
285
|
+
@tls_parms[signature][:cipher_list] = cipher_list.gsub(/,\s*/, ':') if tls_parm_set?(cipher_list)
|
|
286
|
+
@tls_parms[signature][:dhparam] = File.read(dhparam) if tls_parm_set?(dhparam)
|
|
287
|
+
@tls_parms[signature][:ecdh_curve] = ecdh_curve if tls_parm_set?(ecdh_curve)
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
def start_tls signature
|
|
291
|
+
selectable = Reactor.instance.get_selectable(signature) or raise "unknown io selectable for start_tls"
|
|
292
|
+
tls_parms = @tls_parms[signature]
|
|
293
|
+
ctx = OpenSSL::SSL::SSLContext.new
|
|
294
|
+
ctx.options = tls_parms[:ssl_options]
|
|
295
|
+
ctx.cert = DefaultCertificate.cert
|
|
296
|
+
ctx.key = DefaultCertificate.key
|
|
297
|
+
ctx.cert_store = OpenSSL::X509::Store.new
|
|
298
|
+
ctx.cert_store.set_default_paths
|
|
299
|
+
ctx.cert = OpenSSL::X509::Certificate.new(tls_parms[:cert_chain]) if tls_parms[:cert_chain]
|
|
300
|
+
ctx.key = OpenSSL::PKey::RSA.new(tls_parms[:priv_key]) if tls_parms[:priv_key]
|
|
301
|
+
verify_mode = OpenSSL::SSL::VERIFY_NONE
|
|
302
|
+
if tls_parms[:verify_peer]
|
|
303
|
+
verify_mode |= OpenSSL::SSL::VERIFY_PEER
|
|
304
|
+
end
|
|
305
|
+
if tls_parms[:fail_if_no_peer_cert]
|
|
306
|
+
verify_mode |= OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT
|
|
307
|
+
end
|
|
308
|
+
ctx.verify_mode = verify_mode
|
|
309
|
+
ctx.servername_cb = Proc.new do |_, server_name|
|
|
310
|
+
tls_parms[:server_name] = server_name
|
|
311
|
+
nil
|
|
312
|
+
end
|
|
313
|
+
ctx.ciphers = tls_parms[:cipher_list] if tls_parms[:cipher_list]
|
|
314
|
+
if selectable.is_server
|
|
315
|
+
ctx.tmp_dh_callback = Proc.new do |_, _, key_length|
|
|
316
|
+
if tls_parms[:dhparam]
|
|
317
|
+
OpenSSL::PKey::DH.new(tls_parms[:dhparam])
|
|
318
|
+
else
|
|
319
|
+
case key_length
|
|
320
|
+
when 1024 then DefaultDHKey1024
|
|
321
|
+
when 2048 then DefaultDHKey2048
|
|
322
|
+
else
|
|
323
|
+
nil
|
|
324
|
+
end
|
|
325
|
+
end
|
|
326
|
+
end
|
|
327
|
+
if tls_parms[:ecdh_curve] && ctx.respond_to?(:tmp_ecdh_callback)
|
|
328
|
+
ctx.tmp_ecdh_callback = Proc.new do
|
|
329
|
+
OpenSSL::PKey::EC.new(tls_parms[:ecdh_curve])
|
|
330
|
+
end
|
|
331
|
+
end
|
|
332
|
+
end
|
|
333
|
+
ssl_io = OpenSSL::SSL::SSLSocket.new(selectable, ctx)
|
|
334
|
+
ssl_io.sync_close = true
|
|
335
|
+
if tls_parms[:sni_hostname]
|
|
336
|
+
ssl_io.hostname = tls_parms[:sni_hostname] if ssl_io.respond_to?(:hostname=)
|
|
337
|
+
end
|
|
338
|
+
begin
|
|
339
|
+
selectable.is_server ? ssl_io.accept_nonblock : ssl_io.connect_nonblock
|
|
340
|
+
rescue; end
|
|
341
|
+
selectable.io = ssl_io
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
def get_peer_cert signature
|
|
345
|
+
selectable = Reactor.instance.get_selectable(signature) or raise "unknown get_peer_cert target"
|
|
346
|
+
if selectable.io.respond_to?(:peer_cert) && selectable.io.peer_cert
|
|
347
|
+
selectable.io.peer_cert.to_pem
|
|
348
|
+
else
|
|
349
|
+
nil
|
|
350
|
+
end
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
def get_cipher_name signature
|
|
354
|
+
selectable = Reactor.instance.get_selectable(signature) or raise "unknown get_cipher_name target"
|
|
355
|
+
selectable.io.respond_to?(:cipher) ? selectable.io.cipher[0] : nil
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
def get_cipher_protocol signature
|
|
359
|
+
selectable = Reactor.instance.get_selectable(signature) or raise "unknown get_cipher_protocol target"
|
|
360
|
+
selectable.io.respond_to?(:cipher) ? selectable.io.cipher[1] : nil
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
def get_cipher_bits signature
|
|
364
|
+
selectable = Reactor.instance.get_selectable(signature) or raise "unknown get_cipher_bits target"
|
|
365
|
+
selectable.io.respond_to?(:cipher) ? selectable.io.cipher[2] : nil
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
def get_sni_hostname signature
|
|
369
|
+
@tls_parms ||= {}
|
|
370
|
+
if @tls_parms[signature]
|
|
371
|
+
@tls_parms[signature][:server_name]
|
|
372
|
+
else
|
|
373
|
+
nil
|
|
374
|
+
end
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
# This method is a no-op in the pure-Ruby implementation. We simply return Ruby's built-in
|
|
378
|
+
# per-process file-descriptor limit.
|
|
379
|
+
# @private
|
|
380
|
+
def set_rlimit_nofile n
|
|
381
|
+
1024
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
# This method is a harmless no-op in pure Ruby, which doesn't have a built-in limit
|
|
385
|
+
# on the number of available timers.
|
|
386
|
+
# @private
|
|
387
|
+
def set_max_timer_count n
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
# @private
|
|
391
|
+
def get_sock_opt signature, level, optname
|
|
392
|
+
selectable = Reactor.instance.get_selectable( signature ) or raise "unknown get_sock_opt target"
|
|
393
|
+
selectable.getsockopt level, optname
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
# @private
|
|
397
|
+
def set_sock_opt signature, level, optname, optval
|
|
398
|
+
selectable = Reactor.instance.get_selectable( signature ) or raise "unknown set_sock_opt target"
|
|
399
|
+
selectable.setsockopt level, optname, optval
|
|
400
|
+
end
|
|
401
|
+
|
|
402
|
+
# @private
|
|
403
|
+
def send_file_data sig, filename
|
|
404
|
+
sz = File.size(filename)
|
|
405
|
+
raise "file too large" if sz > 32*1024
|
|
406
|
+
data =
|
|
407
|
+
begin
|
|
408
|
+
File.read filename
|
|
409
|
+
rescue
|
|
410
|
+
""
|
|
411
|
+
end
|
|
412
|
+
send_data sig, data, data.length
|
|
413
|
+
end
|
|
414
|
+
|
|
415
|
+
# @private
|
|
416
|
+
def get_outbound_data_size sig
|
|
417
|
+
r = Reactor.instance.get_selectable( sig ) or raise "unknown get_outbound_data_size target"
|
|
418
|
+
r.get_outbound_data_size
|
|
419
|
+
end
|
|
420
|
+
|
|
421
|
+
# @private
|
|
422
|
+
def read_keyboard
|
|
423
|
+
EvmaKeyboard.open.uuid
|
|
424
|
+
end
|
|
425
|
+
|
|
426
|
+
# @private
|
|
427
|
+
def set_comm_inactivity_timeout sig, tm
|
|
428
|
+
r = Reactor.instance.get_selectable( sig ) or raise "unknown set_comm_inactivity_timeout target"
|
|
429
|
+
r.set_inactivity_timeout tm
|
|
430
|
+
end
|
|
431
|
+
|
|
432
|
+
# @private
|
|
433
|
+
def set_pending_connect_timeout sig, tm
|
|
434
|
+
# Needs to be implemented. Currently a no-op stub to allow
|
|
435
|
+
# certain software to operate with the EM pure-ruby.
|
|
436
|
+
end
|
|
437
|
+
|
|
438
|
+
# @private
|
|
439
|
+
def report_connection_error_status signature
|
|
440
|
+
get_sock_opt(signature, Socket::SOL_SOCKET, Socket::SO_ERROR).int
|
|
441
|
+
end
|
|
442
|
+
end
|
|
443
|
+
end
|
|
444
|
+
|
|
445
|
+
module EventMachine
|
|
446
|
+
# @private
|
|
447
|
+
class Connection
|
|
448
|
+
# @private
|
|
449
|
+
def get_outbound_data_size
|
|
450
|
+
EventMachine::get_outbound_data_size @signature
|
|
451
|
+
end
|
|
452
|
+
end
|
|
453
|
+
end
|
|
454
|
+
|
|
455
|
+
module EventMachine
|
|
456
|
+
|
|
457
|
+
# Factored out so we can substitute other implementations
|
|
458
|
+
# here if desired, such as the one in ActiveRBAC.
|
|
459
|
+
# @private
|
|
460
|
+
module UuidGenerator
|
|
461
|
+
def self.generate
|
|
462
|
+
@ix ||= 0
|
|
463
|
+
@ix += 1
|
|
464
|
+
end
|
|
465
|
+
end
|
|
466
|
+
end
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
module EventMachine
|
|
470
|
+
# @private
|
|
471
|
+
TimerFired = 100
|
|
472
|
+
# @private
|
|
473
|
+
ConnectionData = 101
|
|
474
|
+
# @private
|
|
475
|
+
ConnectionUnbound = 102
|
|
476
|
+
# @private
|
|
477
|
+
ConnectionAccepted = 103
|
|
478
|
+
# @private
|
|
479
|
+
ConnectionCompleted = 104
|
|
480
|
+
# @private
|
|
481
|
+
LoopbreakSignalled = 105
|
|
482
|
+
# @private
|
|
483
|
+
ConnectionNotifyReadable = 106
|
|
484
|
+
# @private
|
|
485
|
+
ConnectionNotifyWritable = 107
|
|
486
|
+
# @private
|
|
487
|
+
SslHandshakeCompleted = 108
|
|
488
|
+
# @private
|
|
489
|
+
SslVerify = 109
|
|
490
|
+
# @private
|
|
491
|
+
EM_PROTO_SSLv2 = 2
|
|
492
|
+
# @private
|
|
493
|
+
EM_PROTO_SSLv3 = 4
|
|
494
|
+
# @private
|
|
495
|
+
EM_PROTO_TLSv1 = 8
|
|
496
|
+
# @private
|
|
497
|
+
EM_PROTO_TLSv1_1 = 16
|
|
498
|
+
# @private
|
|
499
|
+
EM_PROTO_TLSv1_2 = 32
|
|
500
|
+
end
|
|
501
|
+
|
|
502
|
+
module EventMachine
|
|
503
|
+
# @private
|
|
504
|
+
class Reactor
|
|
505
|
+
include Singleton
|
|
506
|
+
|
|
507
|
+
HeartbeatInterval = 2
|
|
508
|
+
|
|
509
|
+
attr_reader :current_loop_time, :stop_scheduled
|
|
510
|
+
|
|
511
|
+
def initialize
|
|
512
|
+
initialize_for_run
|
|
513
|
+
end
|
|
514
|
+
|
|
515
|
+
def get_timer_count
|
|
516
|
+
@timers.size
|
|
517
|
+
end
|
|
518
|
+
|
|
519
|
+
def install_oneshot_timer interval
|
|
520
|
+
uuid = UuidGenerator::generate
|
|
521
|
+
#@timers << [Time.now + interval, uuid]
|
|
522
|
+
#@timers.sort! {|a,b| a.first <=> b.first}
|
|
523
|
+
@timers.add([Time.now + interval, uuid])
|
|
524
|
+
uuid
|
|
525
|
+
end
|
|
526
|
+
|
|
527
|
+
# Called before run, this is a good place to clear out arrays
|
|
528
|
+
# with cruft that may be left over from a previous run.
|
|
529
|
+
# @private
|
|
530
|
+
def initialize_for_run
|
|
531
|
+
@running = false
|
|
532
|
+
@stop_scheduled = false
|
|
533
|
+
@selectables ||= {}; @selectables.clear
|
|
534
|
+
@timers = SortedSet.new # []
|
|
535
|
+
set_timer_quantum(0.1)
|
|
536
|
+
@current_loop_time = Time.now
|
|
537
|
+
@next_heartbeat = @current_loop_time + HeartbeatInterval
|
|
538
|
+
end
|
|
539
|
+
|
|
540
|
+
def add_selectable io
|
|
541
|
+
@selectables[io.uuid] = io
|
|
542
|
+
end
|
|
543
|
+
|
|
544
|
+
def get_selectable uuid
|
|
545
|
+
#raise Error.new("selectable does not exist") if @selectables[uuid].nil?
|
|
546
|
+
#puts "selectables are #{@selectables}"
|
|
547
|
+
@selectables[uuid]
|
|
548
|
+
end
|
|
549
|
+
|
|
550
|
+
def run
|
|
551
|
+
raise Error.new( "already running" ) if @running
|
|
552
|
+
puts "-------- RUNNING ------------ "
|
|
553
|
+
@running = true
|
|
554
|
+
|
|
555
|
+
begin
|
|
556
|
+
open_loopbreaker
|
|
557
|
+
|
|
558
|
+
loop {
|
|
559
|
+
@current_loop_time = Time.now
|
|
560
|
+
|
|
561
|
+
break if @stop_scheduled
|
|
562
|
+
run_timers
|
|
563
|
+
break if @stop_scheduled
|
|
564
|
+
crank_selectables
|
|
565
|
+
break if @stop_scheduled
|
|
566
|
+
run_heartbeats
|
|
567
|
+
}
|
|
568
|
+
ensure
|
|
569
|
+
close_loopbreaker
|
|
570
|
+
@selectables.each {|k, io| io.close}
|
|
571
|
+
@selectables.clear
|
|
572
|
+
|
|
573
|
+
@running = false
|
|
574
|
+
end
|
|
575
|
+
|
|
576
|
+
end
|
|
577
|
+
|
|
578
|
+
def run_timers
|
|
579
|
+
timers_to_delete = []
|
|
580
|
+
@timers.each {|t|
|
|
581
|
+
if t.first <= @current_loop_time
|
|
582
|
+
#@timers.delete t
|
|
583
|
+
timers_to_delete << t
|
|
584
|
+
EventMachine::event_callback "", TimerFired, t.last
|
|
585
|
+
else
|
|
586
|
+
break
|
|
587
|
+
end
|
|
588
|
+
}
|
|
589
|
+
timers_to_delete.map{|c| @timers.delete c}
|
|
590
|
+
timers_to_delete = nil
|
|
591
|
+
#while @timers.length > 0 and @timers.first.first <= now
|
|
592
|
+
# t = @timers.shift
|
|
593
|
+
# EventMachine::event_callback "", TimerFired, t.last
|
|
594
|
+
#end
|
|
595
|
+
end
|
|
596
|
+
|
|
597
|
+
def run_heartbeats
|
|
598
|
+
if @next_heartbeat <= @current_loop_time
|
|
599
|
+
@next_heartbeat = @current_loop_time + HeartbeatInterval
|
|
600
|
+
@selectables.each {|k,io| io.heartbeat}
|
|
601
|
+
end
|
|
602
|
+
end
|
|
603
|
+
|
|
604
|
+
def crank_selectables
|
|
605
|
+
#$stderr.write 'R'
|
|
606
|
+
|
|
607
|
+
readers = @selectables.values.select {|io| io.select_for_reading?}
|
|
608
|
+
writers = @selectables.values.select {|io| io.select_for_writing?}
|
|
609
|
+
|
|
610
|
+
s = select( readers, writers, nil, @timer_quantum)
|
|
611
|
+
|
|
612
|
+
s and s[1] and s[1].each {|w| w.eventable_write }
|
|
613
|
+
s and s[0] and s[0].each {|r| r.eventable_read }
|
|
614
|
+
|
|
615
|
+
@selectables.delete_if {|k,io|
|
|
616
|
+
if io.close_scheduled?
|
|
617
|
+
io.close
|
|
618
|
+
begin
|
|
619
|
+
EventMachine::event_callback io.uuid, ConnectionUnbound, nil
|
|
620
|
+
rescue ConnectionNotBound; end
|
|
621
|
+
true
|
|
622
|
+
end
|
|
623
|
+
}
|
|
624
|
+
end
|
|
625
|
+
|
|
626
|
+
# #stop
|
|
627
|
+
def stop
|
|
628
|
+
raise Error.new( "not running") unless @running
|
|
629
|
+
@stop_scheduled = true
|
|
630
|
+
end
|
|
631
|
+
|
|
632
|
+
def open_loopbreaker
|
|
633
|
+
# Can't use an IO.pipe because they can't be set nonselectable in Windows.
|
|
634
|
+
# Pick a random localhost UDP port.
|
|
635
|
+
#@loopbreak_writer.close if @loopbreak_writer
|
|
636
|
+
#rd,@loopbreak_writer = IO.pipe
|
|
637
|
+
@loopbreak_reader = UDPSocket.new
|
|
638
|
+
@loopbreak_writer = UDPSocket.new
|
|
639
|
+
bound = false
|
|
640
|
+
100.times {
|
|
641
|
+
@loopbreak_port = rand(10000) + 40000
|
|
642
|
+
begin
|
|
643
|
+
@loopbreak_reader.bind "127.0.0.1", @loopbreak_port
|
|
644
|
+
bound = true
|
|
645
|
+
break
|
|
646
|
+
rescue
|
|
647
|
+
end
|
|
648
|
+
}
|
|
649
|
+
raise "Unable to bind Loopbreaker" unless bound
|
|
650
|
+
LoopbreakReader.new(@loopbreak_reader)
|
|
651
|
+
end
|
|
652
|
+
|
|
653
|
+
def close_loopbreaker
|
|
654
|
+
@loopbreak_writer.close
|
|
655
|
+
@loopbreak_writer = nil
|
|
656
|
+
end
|
|
657
|
+
|
|
658
|
+
def signal_loopbreak
|
|
659
|
+
begin
|
|
660
|
+
@loopbreak_writer.send('+',0,"127.0.0.1",@loopbreak_port) if @loopbreak_writer
|
|
661
|
+
rescue IOError; end
|
|
662
|
+
end
|
|
663
|
+
|
|
664
|
+
def set_timer_quantum interval_in_seconds
|
|
665
|
+
@timer_quantum = interval_in_seconds
|
|
666
|
+
end
|
|
667
|
+
|
|
668
|
+
end
|
|
669
|
+
|
|
670
|
+
end
|
|
671
|
+
|
|
672
|
+
# @private
|
|
673
|
+
class IO
|
|
674
|
+
extend Forwardable
|
|
675
|
+
def_delegator :@my_selectable, :close_scheduled?
|
|
676
|
+
def_delegator :@my_selectable, :select_for_reading?
|
|
677
|
+
def_delegator :@my_selectable, :select_for_writing?
|
|
678
|
+
def_delegator :@my_selectable, :eventable_read
|
|
679
|
+
def_delegator :@my_selectable, :eventable_write
|
|
680
|
+
def_delegator :@my_selectable, :uuid
|
|
681
|
+
def_delegator :@my_selectable, :is_server
|
|
682
|
+
def_delegator :@my_selectable, :is_server=
|
|
683
|
+
def_delegator :@my_selectable, :send_data
|
|
684
|
+
def_delegator :@my_selectable, :schedule_close
|
|
685
|
+
def_delegator :@my_selectable, :get_peername
|
|
686
|
+
def_delegator :@my_selectable, :get_sockname
|
|
687
|
+
def_delegator :@my_selectable, :send_datagram
|
|
688
|
+
def_delegator :@my_selectable, :get_outbound_data_size
|
|
689
|
+
def_delegator :@my_selectable, :set_inactivity_timeout
|
|
690
|
+
def_delegator :@my_selectable, :heartbeat
|
|
691
|
+
def_delegator :@my_selectable, :io
|
|
692
|
+
def_delegator :@my_selectable, :io=
|
|
693
|
+
end
|
|
694
|
+
|
|
695
|
+
module EventMachine
|
|
696
|
+
# @private
|
|
697
|
+
class Selectable
|
|
698
|
+
|
|
699
|
+
attr_accessor :io, :is_server
|
|
700
|
+
attr_reader :uuid
|
|
701
|
+
|
|
702
|
+
def initialize io
|
|
703
|
+
@io = io
|
|
704
|
+
@uuid = UuidGenerator.generate
|
|
705
|
+
@is_server = false
|
|
706
|
+
@last_activity = Reactor.instance.current_loop_time
|
|
707
|
+
|
|
708
|
+
if defined?(Fcntl::F_GETFL)
|
|
709
|
+
m = @io.fcntl(Fcntl::F_GETFL, 0)
|
|
710
|
+
@io.fcntl(Fcntl::F_SETFL, Fcntl::O_NONBLOCK | m)
|
|
711
|
+
else
|
|
712
|
+
# Windows doesn't define F_GETFL.
|
|
713
|
+
# It's not very reliable about setting descriptors nonblocking either.
|
|
714
|
+
begin
|
|
715
|
+
s = Socket.for_fd(@io.fileno)
|
|
716
|
+
s.fcntl( Fcntl::F_SETFL, Fcntl::O_NONBLOCK )
|
|
717
|
+
rescue Errno::EINVAL, Errno::EBADF
|
|
718
|
+
warn "Serious error: unable to set descriptor non-blocking"
|
|
719
|
+
end
|
|
720
|
+
end
|
|
721
|
+
# TODO, should set CLOEXEC on Unix?
|
|
722
|
+
|
|
723
|
+
@close_scheduled = false
|
|
724
|
+
@close_requested = false
|
|
725
|
+
|
|
726
|
+
se = self; @io.instance_eval { @my_selectable = se }
|
|
727
|
+
Reactor.instance.add_selectable @io
|
|
728
|
+
end
|
|
729
|
+
|
|
730
|
+
def close_scheduled?
|
|
731
|
+
@close_scheduled
|
|
732
|
+
end
|
|
733
|
+
|
|
734
|
+
def select_for_reading?
|
|
735
|
+
false
|
|
736
|
+
end
|
|
737
|
+
|
|
738
|
+
def select_for_writing?
|
|
739
|
+
false
|
|
740
|
+
end
|
|
741
|
+
|
|
742
|
+
def get_peername
|
|
743
|
+
nil
|
|
744
|
+
end
|
|
745
|
+
|
|
746
|
+
def get_sockname
|
|
747
|
+
nil
|
|
748
|
+
end
|
|
749
|
+
|
|
750
|
+
def set_inactivity_timeout tm
|
|
751
|
+
@inactivity_timeout = tm
|
|
752
|
+
end
|
|
753
|
+
|
|
754
|
+
def heartbeat
|
|
755
|
+
end
|
|
756
|
+
|
|
757
|
+
def schedule_close(after_writing=false)
|
|
758
|
+
if after_writing
|
|
759
|
+
@close_requested = true
|
|
760
|
+
else
|
|
761
|
+
@close_scheduled = true
|
|
762
|
+
end
|
|
763
|
+
end
|
|
764
|
+
end
|
|
765
|
+
|
|
766
|
+
end
|
|
767
|
+
|
|
768
|
+
module EventMachine
|
|
769
|
+
# @private
|
|
770
|
+
class StreamObject < Selectable
|
|
771
|
+
def initialize io
|
|
772
|
+
super io
|
|
773
|
+
@outbound_q = []
|
|
774
|
+
end
|
|
775
|
+
|
|
776
|
+
# If we have to close, or a close-after-writing has been requested,
|
|
777
|
+
# then don't read any more data.
|
|
778
|
+
def select_for_reading?
|
|
779
|
+
true unless (@close_scheduled || @close_requested)
|
|
780
|
+
end
|
|
781
|
+
|
|
782
|
+
# If we have to close, don't select for writing.
|
|
783
|
+
# Otherwise, see if the protocol is ready to close.
|
|
784
|
+
# If not, see if he has data to send.
|
|
785
|
+
# If a close-after-writing has been requested and the outbound queue
|
|
786
|
+
# is empty, convert the status to close_scheduled.
|
|
787
|
+
def select_for_writing?
|
|
788
|
+
unless @close_scheduled
|
|
789
|
+
if @outbound_q.empty?
|
|
790
|
+
@close_scheduled = true if @close_requested
|
|
791
|
+
false
|
|
792
|
+
else
|
|
793
|
+
true
|
|
794
|
+
end
|
|
795
|
+
end
|
|
796
|
+
end
|
|
797
|
+
|
|
798
|
+
# Proper nonblocking I/O was added to Ruby 1.8.4 in May 2006.
|
|
799
|
+
# If we have it, then we can read multiple times safely to improve
|
|
800
|
+
# performance.
|
|
801
|
+
# The last-activity clock ASSUMES that we only come here when we
|
|
802
|
+
# have selected readable.
|
|
803
|
+
# TODO, coalesce multiple reads into a single event.
|
|
804
|
+
# TODO, do the function check somewhere else and cache it.
|
|
805
|
+
def eventable_read
|
|
806
|
+
@last_activity = Reactor.instance.current_loop_time
|
|
807
|
+
begin
|
|
808
|
+
if io.respond_to?(:read_nonblock)
|
|
809
|
+
10.times {
|
|
810
|
+
data = io.read_nonblock(4096)
|
|
811
|
+
EventMachine::event_callback uuid, ConnectionData, data
|
|
812
|
+
}
|
|
813
|
+
else
|
|
814
|
+
data = io.sysread(4096)
|
|
815
|
+
EventMachine::event_callback uuid, ConnectionData, data
|
|
816
|
+
end
|
|
817
|
+
rescue Errno::EAGAIN, Errno::EWOULDBLOCK, SSLConnectionWaitReadable
|
|
818
|
+
# no-op
|
|
819
|
+
rescue Errno::ECONNRESET, Errno::ECONNREFUSED, EOFError, Errno::EPIPE, OpenSSL::SSL::SSLError
|
|
820
|
+
@close_scheduled = true
|
|
821
|
+
EventMachine::event_callback uuid, ConnectionUnbound, nil
|
|
822
|
+
end
|
|
823
|
+
|
|
824
|
+
end
|
|
825
|
+
|
|
826
|
+
# Provisional implementation. Will be re-implemented in subclasses.
|
|
827
|
+
# TODO: Complete this implementation. As it stands, this only writes
|
|
828
|
+
# a single packet per cycle. Highly inefficient, but required unless
|
|
829
|
+
# we're running on a Ruby with proper nonblocking I/O (Ruby 1.8.4
|
|
830
|
+
# built from sources from May 25, 2006 or newer).
|
|
831
|
+
# We need to improve the loop so it writes multiple times, however
|
|
832
|
+
# not more than a certain number of bytes per cycle, otherwise
|
|
833
|
+
# one busy connection could hog output buffers and slow down other
|
|
834
|
+
# connections. Also we should coalesce small writes.
|
|
835
|
+
# URGENT TODO: Coalesce small writes. They are a performance killer.
|
|
836
|
+
# The last-activity recorder ASSUMES we'll only come here if we've
|
|
837
|
+
# selected writable.
|
|
838
|
+
def eventable_write
|
|
839
|
+
# coalesce the outbound array here, perhaps
|
|
840
|
+
@last_activity = Reactor.instance.current_loop_time
|
|
841
|
+
while data = @outbound_q.shift do
|
|
842
|
+
begin
|
|
843
|
+
data = data.to_s
|
|
844
|
+
w = if io.respond_to?(:write_nonblock)
|
|
845
|
+
io.write_nonblock data
|
|
846
|
+
else
|
|
847
|
+
io.syswrite data
|
|
848
|
+
end
|
|
849
|
+
|
|
850
|
+
if w < data.length
|
|
851
|
+
@outbound_q.unshift data[w..-1]
|
|
852
|
+
break
|
|
853
|
+
end
|
|
854
|
+
rescue Errno::EAGAIN, SSLConnectionWaitReadable, SSLConnectionWaitWritable
|
|
855
|
+
@outbound_q.unshift data
|
|
856
|
+
break
|
|
857
|
+
rescue EOFError, Errno::ECONNRESET, Errno::ECONNREFUSED, Errno::EPIPE, OpenSSL::SSL::SSLError
|
|
858
|
+
@close_scheduled = true
|
|
859
|
+
@outbound_q.clear
|
|
860
|
+
end
|
|
861
|
+
end
|
|
862
|
+
|
|
863
|
+
end
|
|
864
|
+
|
|
865
|
+
# #send_data
|
|
866
|
+
def send_data data
|
|
867
|
+
# TODO, coalesce here perhaps by being smarter about appending to @outbound_q.last?
|
|
868
|
+
unless @close_scheduled or @close_requested or !data or data.length <= 0
|
|
869
|
+
@outbound_q << data.to_s
|
|
870
|
+
end
|
|
871
|
+
end
|
|
872
|
+
|
|
873
|
+
# #get_peername
|
|
874
|
+
# This is defined in the normal way on connected stream objects.
|
|
875
|
+
# Return an object that is suitable for passing to Socket#unpack_sockaddr_in or variants.
|
|
876
|
+
# We could also use a convenience method that did the unpacking automatically.
|
|
877
|
+
def get_peername
|
|
878
|
+
io.getpeername
|
|
879
|
+
end
|
|
880
|
+
|
|
881
|
+
# #get_sockname
|
|
882
|
+
# This is defined in the normal way on connected stream objects.
|
|
883
|
+
# Return an object that is suitable for passing to Socket#unpack_sockaddr_in or variants.
|
|
884
|
+
# We could also use a convenience method that did the unpacking automatically.
|
|
885
|
+
def get_sockname
|
|
886
|
+
io.getsockname
|
|
887
|
+
end
|
|
888
|
+
|
|
889
|
+
# #get_outbound_data_size
|
|
890
|
+
def get_outbound_data_size
|
|
891
|
+
@outbound_q.inject(0) {|memo,obj| memo += (obj || "").length}
|
|
892
|
+
end
|
|
893
|
+
|
|
894
|
+
def heartbeat
|
|
895
|
+
if @inactivity_timeout and @inactivity_timeout > 0 and (@last_activity + @inactivity_timeout) < Reactor.instance.current_loop_time
|
|
896
|
+
schedule_close true
|
|
897
|
+
end
|
|
898
|
+
end
|
|
899
|
+
end
|
|
900
|
+
|
|
901
|
+
|
|
902
|
+
end
|
|
903
|
+
|
|
904
|
+
|
|
905
|
+
#--------------------------------------------------------------
|
|
906
|
+
|
|
907
|
+
|
|
908
|
+
|
|
909
|
+
module EventMachine
|
|
910
|
+
# @private
|
|
911
|
+
class EvmaTCPClient < StreamObject
|
|
912
|
+
|
|
913
|
+
def self.connect bind_addr, bind_port, host, port
|
|
914
|
+
sd = Socket.new( Socket::AF_INET, Socket::SOCK_STREAM, 0 )
|
|
915
|
+
sd.bind( Socket.pack_sockaddr_in( bind_port, bind_addr )) if bind_addr
|
|
916
|
+
|
|
917
|
+
begin
|
|
918
|
+
# TODO, this assumes a current Ruby snapshot.
|
|
919
|
+
# We need to degrade to a nonblocking connect otherwise.
|
|
920
|
+
sd.connect_nonblock( Socket.pack_sockaddr_in( port, host ))
|
|
921
|
+
rescue Errno::ECONNREFUSED, Errno::EINPROGRESS
|
|
922
|
+
end
|
|
923
|
+
EvmaTCPClient.new sd
|
|
924
|
+
end
|
|
925
|
+
|
|
926
|
+
def initialize io
|
|
927
|
+
super
|
|
928
|
+
@pending = true
|
|
929
|
+
@handshake_complete = false
|
|
930
|
+
end
|
|
931
|
+
|
|
932
|
+
def ready?
|
|
933
|
+
if RUBY_PLATFORM =~ /linux/
|
|
934
|
+
io.getsockopt(Socket::SOL_TCP, Socket::TCP_INFO).unpack("i").first == 1 # TCP_ESTABLISHED
|
|
935
|
+
else
|
|
936
|
+
io.getsockopt(Socket::SOL_SOCKET, Socket::SO_ERROR).unpack("i").first == 0 # NO ERROR
|
|
937
|
+
end
|
|
938
|
+
end
|
|
939
|
+
|
|
940
|
+
def handshake_complete?
|
|
941
|
+
if !@handshake_complete && io.respond_to?(:state)
|
|
942
|
+
if io.state =~ /^SSLOK/
|
|
943
|
+
@handshake_complete = true
|
|
944
|
+
EventMachine::event_callback uuid, SslHandshakeCompleted, ""
|
|
945
|
+
EventMachine::event_callback uuid, SslVerify, io.peer_cert.to_pem if io.peer_cert
|
|
946
|
+
end
|
|
947
|
+
else
|
|
948
|
+
@handshake_complete = true
|
|
949
|
+
end
|
|
950
|
+
@handshake_complete
|
|
951
|
+
end
|
|
952
|
+
|
|
953
|
+
def pending?
|
|
954
|
+
handshake_complete?
|
|
955
|
+
if @pending
|
|
956
|
+
if ready?
|
|
957
|
+
@pending = false
|
|
958
|
+
EventMachine::event_callback uuid, ConnectionCompleted, ""
|
|
959
|
+
end
|
|
960
|
+
end
|
|
961
|
+
@pending
|
|
962
|
+
end
|
|
963
|
+
|
|
964
|
+
def select_for_writing?
|
|
965
|
+
pending?
|
|
966
|
+
super
|
|
967
|
+
end
|
|
968
|
+
|
|
969
|
+
def select_for_reading?
|
|
970
|
+
pending?
|
|
971
|
+
super
|
|
972
|
+
end
|
|
973
|
+
end
|
|
974
|
+
end
|
|
975
|
+
|
|
976
|
+
|
|
977
|
+
|
|
978
|
+
module EventMachine
|
|
979
|
+
# @private
|
|
980
|
+
class EvmaKeyboard < StreamObject
|
|
981
|
+
|
|
982
|
+
def self.open
|
|
983
|
+
EvmaKeyboard.new STDIN
|
|
984
|
+
end
|
|
985
|
+
|
|
986
|
+
|
|
987
|
+
def initialize io
|
|
988
|
+
super
|
|
989
|
+
end
|
|
990
|
+
|
|
991
|
+
|
|
992
|
+
def select_for_writing?
|
|
993
|
+
false
|
|
994
|
+
end
|
|
995
|
+
|
|
996
|
+
def select_for_reading?
|
|
997
|
+
true
|
|
998
|
+
end
|
|
999
|
+
|
|
1000
|
+
|
|
1001
|
+
end
|
|
1002
|
+
end
|
|
1003
|
+
|
|
1004
|
+
|
|
1005
|
+
|
|
1006
|
+
module EventMachine
|
|
1007
|
+
# @private
|
|
1008
|
+
class EvmaUNIXClient < StreamObject
|
|
1009
|
+
|
|
1010
|
+
def self.connect chain
|
|
1011
|
+
sd = Socket.new( Socket::AF_LOCAL, Socket::SOCK_STREAM, 0 )
|
|
1012
|
+
begin
|
|
1013
|
+
# TODO, this assumes a current Ruby snapshot.
|
|
1014
|
+
# We need to degrade to a nonblocking connect otherwise.
|
|
1015
|
+
sd.connect_nonblock( Socket.pack_sockaddr_un( chain ))
|
|
1016
|
+
rescue Errno::EINPROGRESS
|
|
1017
|
+
end
|
|
1018
|
+
EvmaUNIXClient.new sd
|
|
1019
|
+
end
|
|
1020
|
+
|
|
1021
|
+
|
|
1022
|
+
def initialize io
|
|
1023
|
+
super
|
|
1024
|
+
@pending = true
|
|
1025
|
+
end
|
|
1026
|
+
|
|
1027
|
+
|
|
1028
|
+
def select_for_writing?
|
|
1029
|
+
@pending ? true : super
|
|
1030
|
+
end
|
|
1031
|
+
|
|
1032
|
+
def select_for_reading?
|
|
1033
|
+
@pending ? false : super
|
|
1034
|
+
end
|
|
1035
|
+
|
|
1036
|
+
def eventable_write
|
|
1037
|
+
if @pending
|
|
1038
|
+
@pending = false
|
|
1039
|
+
if 0 == io.getsockopt(Socket::SOL_SOCKET, Socket::SO_ERROR).unpack("i").first
|
|
1040
|
+
EventMachine::event_callback uuid, ConnectionCompleted, ""
|
|
1041
|
+
end
|
|
1042
|
+
else
|
|
1043
|
+
super
|
|
1044
|
+
end
|
|
1045
|
+
end
|
|
1046
|
+
|
|
1047
|
+
|
|
1048
|
+
|
|
1049
|
+
end
|
|
1050
|
+
end
|
|
1051
|
+
|
|
1052
|
+
|
|
1053
|
+
#--------------------------------------------------------------
|
|
1054
|
+
|
|
1055
|
+
module EventMachine
|
|
1056
|
+
# @private
|
|
1057
|
+
class EvmaTCPServer < Selectable
|
|
1058
|
+
|
|
1059
|
+
# TODO, refactor and unify with EvmaUNIXServer.
|
|
1060
|
+
|
|
1061
|
+
class << self
|
|
1062
|
+
# Versions of ruby 1.8.4 later than May 26 2006 will work properly
|
|
1063
|
+
# with an object of type TCPServer. Prior versions won't so we
|
|
1064
|
+
# play it safe and just build a socket.
|
|
1065
|
+
#
|
|
1066
|
+
def start_server host, port
|
|
1067
|
+
sd = Socket.new( Socket::AF_INET, Socket::SOCK_STREAM, 0 )
|
|
1068
|
+
sd.setsockopt( Socket::SOL_SOCKET, Socket::SO_REUSEADDR, true )
|
|
1069
|
+
sd.bind( Socket.pack_sockaddr_in( port, host ))
|
|
1070
|
+
sd.listen( 50 ) # 5 is what you see in all the books. Ain't enough.
|
|
1071
|
+
EvmaTCPServer.new sd
|
|
1072
|
+
end
|
|
1073
|
+
end
|
|
1074
|
+
|
|
1075
|
+
def initialize io
|
|
1076
|
+
super io
|
|
1077
|
+
end
|
|
1078
|
+
|
|
1079
|
+
|
|
1080
|
+
def select_for_reading?
|
|
1081
|
+
true
|
|
1082
|
+
end
|
|
1083
|
+
|
|
1084
|
+
#--
|
|
1085
|
+
# accept_nonblock returns an array consisting of the accepted
|
|
1086
|
+
# socket and a sockaddr_in which names the peer.
|
|
1087
|
+
# Don't accept more than 10 at a time.
|
|
1088
|
+
def eventable_read
|
|
1089
|
+
begin
|
|
1090
|
+
10.times {
|
|
1091
|
+
descriptor,peername = io.accept_nonblock
|
|
1092
|
+
sd = EvmaTCPClient.new descriptor
|
|
1093
|
+
sd.is_server = true
|
|
1094
|
+
EventMachine::event_callback uuid, ConnectionAccepted, sd.uuid
|
|
1095
|
+
}
|
|
1096
|
+
rescue Errno::EWOULDBLOCK, Errno::EAGAIN
|
|
1097
|
+
end
|
|
1098
|
+
end
|
|
1099
|
+
|
|
1100
|
+
#--
|
|
1101
|
+
#
|
|
1102
|
+
def schedule_close
|
|
1103
|
+
@close_scheduled = true
|
|
1104
|
+
end
|
|
1105
|
+
|
|
1106
|
+
end
|
|
1107
|
+
end
|
|
1108
|
+
|
|
1109
|
+
|
|
1110
|
+
#--------------------------------------------------------------
|
|
1111
|
+
|
|
1112
|
+
module EventMachine
|
|
1113
|
+
# @private
|
|
1114
|
+
class EvmaUNIXServer < Selectable
|
|
1115
|
+
|
|
1116
|
+
# TODO, refactor and unify with EvmaTCPServer.
|
|
1117
|
+
|
|
1118
|
+
class << self
|
|
1119
|
+
# Versions of ruby 1.8.4 later than May 26 2006 will work properly
|
|
1120
|
+
# with an object of type TCPServer. Prior versions won't so we
|
|
1121
|
+
# play it safe and just build a socket.
|
|
1122
|
+
#
|
|
1123
|
+
def start_server chain
|
|
1124
|
+
sd = Socket.new( Socket::AF_LOCAL, Socket::SOCK_STREAM, 0 )
|
|
1125
|
+
sd.setsockopt( Socket::SOL_SOCKET, Socket::SO_REUSEADDR, true )
|
|
1126
|
+
sd.bind( Socket.pack_sockaddr_un( chain ))
|
|
1127
|
+
sd.listen( 50 ) # 5 is what you see in all the books. Ain't enough.
|
|
1128
|
+
EvmaUNIXServer.new sd
|
|
1129
|
+
end
|
|
1130
|
+
end
|
|
1131
|
+
|
|
1132
|
+
def initialize io
|
|
1133
|
+
super io
|
|
1134
|
+
end
|
|
1135
|
+
|
|
1136
|
+
|
|
1137
|
+
def select_for_reading?
|
|
1138
|
+
true
|
|
1139
|
+
end
|
|
1140
|
+
|
|
1141
|
+
#--
|
|
1142
|
+
# accept_nonblock returns an array consisting of the accepted
|
|
1143
|
+
# socket and a sockaddr_in which names the peer.
|
|
1144
|
+
# Don't accept more than 10 at a time.
|
|
1145
|
+
def eventable_read
|
|
1146
|
+
begin
|
|
1147
|
+
10.times {
|
|
1148
|
+
descriptor,peername = io.accept_nonblock
|
|
1149
|
+
sd = StreamObject.new descriptor
|
|
1150
|
+
EventMachine::event_callback uuid, ConnectionAccepted, sd.uuid
|
|
1151
|
+
}
|
|
1152
|
+
rescue Errno::EWOULDBLOCK, Errno::EAGAIN
|
|
1153
|
+
end
|
|
1154
|
+
end
|
|
1155
|
+
|
|
1156
|
+
#--
|
|
1157
|
+
#
|
|
1158
|
+
def schedule_close
|
|
1159
|
+
@close_scheduled = true
|
|
1160
|
+
end
|
|
1161
|
+
|
|
1162
|
+
end
|
|
1163
|
+
end
|
|
1164
|
+
|
|
1165
|
+
|
|
1166
|
+
|
|
1167
|
+
#--------------------------------------------------------------
|
|
1168
|
+
|
|
1169
|
+
module EventMachine
|
|
1170
|
+
# @private
|
|
1171
|
+
class LoopbreakReader < Selectable
|
|
1172
|
+
|
|
1173
|
+
def select_for_reading?
|
|
1174
|
+
true
|
|
1175
|
+
end
|
|
1176
|
+
|
|
1177
|
+
def eventable_read
|
|
1178
|
+
io.sysread(128)
|
|
1179
|
+
EventMachine::event_callback "", LoopbreakSignalled, ""
|
|
1180
|
+
end
|
|
1181
|
+
|
|
1182
|
+
end
|
|
1183
|
+
end
|
|
1184
|
+
|
|
1185
|
+
|
|
1186
|
+
|
|
1187
|
+
# @private
|
|
1188
|
+
module EventMachine
|
|
1189
|
+
# @private
|
|
1190
|
+
class DatagramObject < Selectable
|
|
1191
|
+
def initialize io
|
|
1192
|
+
super io
|
|
1193
|
+
@outbound_q = []
|
|
1194
|
+
end
|
|
1195
|
+
|
|
1196
|
+
# #send_datagram
|
|
1197
|
+
def send_datagram data, target
|
|
1198
|
+
# TODO, coalesce here perhaps by being smarter about appending to @outbound_q.last?
|
|
1199
|
+
unless @close_scheduled or @close_requested
|
|
1200
|
+
@outbound_q << [data.to_s, target]
|
|
1201
|
+
end
|
|
1202
|
+
end
|
|
1203
|
+
|
|
1204
|
+
# #select_for_writing?
|
|
1205
|
+
def select_for_writing?
|
|
1206
|
+
unless @close_scheduled
|
|
1207
|
+
if @outbound_q.empty?
|
|
1208
|
+
@close_scheduled = true if @close_requested
|
|
1209
|
+
false
|
|
1210
|
+
else
|
|
1211
|
+
true
|
|
1212
|
+
end
|
|
1213
|
+
end
|
|
1214
|
+
end
|
|
1215
|
+
|
|
1216
|
+
# #select_for_reading?
|
|
1217
|
+
def select_for_reading?
|
|
1218
|
+
true
|
|
1219
|
+
end
|
|
1220
|
+
|
|
1221
|
+
# #get_outbound_data_size
|
|
1222
|
+
def get_outbound_data_size
|
|
1223
|
+
@outbound_q.inject(0) {|memo,obj| memo += (obj || "").length}
|
|
1224
|
+
end
|
|
1225
|
+
|
|
1226
|
+
|
|
1227
|
+
end
|
|
1228
|
+
|
|
1229
|
+
|
|
1230
|
+
end
|
|
1231
|
+
|
|
1232
|
+
|
|
1233
|
+
module EventMachine
|
|
1234
|
+
# @private
|
|
1235
|
+
class EvmaUDPSocket < DatagramObject
|
|
1236
|
+
|
|
1237
|
+
class << self
|
|
1238
|
+
def create host, port
|
|
1239
|
+
sd = Socket.new( Socket::AF_INET, Socket::SOCK_DGRAM, 0 )
|
|
1240
|
+
sd.bind Socket::pack_sockaddr_in( port, host )
|
|
1241
|
+
EvmaUDPSocket.new sd
|
|
1242
|
+
end
|
|
1243
|
+
end
|
|
1244
|
+
|
|
1245
|
+
# #eventable_write
|
|
1246
|
+
# This really belongs in DatagramObject, but there is some UDP-specific stuff.
|
|
1247
|
+
def eventable_write
|
|
1248
|
+
40.times {
|
|
1249
|
+
break if @outbound_q.empty?
|
|
1250
|
+
begin
|
|
1251
|
+
data,target = @outbound_q.first
|
|
1252
|
+
|
|
1253
|
+
# This damn better be nonblocking.
|
|
1254
|
+
io.send data.to_s, 0, target
|
|
1255
|
+
|
|
1256
|
+
@outbound_q.shift
|
|
1257
|
+
rescue Errno::EAGAIN
|
|
1258
|
+
# It's not been observed in testing that we ever get here.
|
|
1259
|
+
# True to the definition, packets will be accepted and quietly dropped
|
|
1260
|
+
# if the system is under pressure.
|
|
1261
|
+
break
|
|
1262
|
+
rescue EOFError, Errno::ECONNRESET
|
|
1263
|
+
@close_scheduled = true
|
|
1264
|
+
@outbound_q.clear
|
|
1265
|
+
end
|
|
1266
|
+
}
|
|
1267
|
+
end
|
|
1268
|
+
|
|
1269
|
+
# Proper nonblocking I/O was added to Ruby 1.8.4 in May 2006.
|
|
1270
|
+
# If we have it, then we can read multiple times safely to improve
|
|
1271
|
+
# performance.
|
|
1272
|
+
def eventable_read
|
|
1273
|
+
begin
|
|
1274
|
+
if io.respond_to?(:recvfrom_nonblock)
|
|
1275
|
+
40.times {
|
|
1276
|
+
data,@return_address = io.recvfrom_nonblock(16384)
|
|
1277
|
+
EventMachine::event_callback uuid, ConnectionData, data
|
|
1278
|
+
@return_address = nil
|
|
1279
|
+
}
|
|
1280
|
+
else
|
|
1281
|
+
raise "unimplemented datagram-read operation on this Ruby"
|
|
1282
|
+
end
|
|
1283
|
+
rescue Errno::EAGAIN
|
|
1284
|
+
# no-op
|
|
1285
|
+
rescue Errno::ECONNRESET, EOFError
|
|
1286
|
+
@close_scheduled = true
|
|
1287
|
+
EventMachine::event_callback uuid, ConnectionUnbound, nil
|
|
1288
|
+
end
|
|
1289
|
+
end
|
|
1290
|
+
|
|
1291
|
+
def send_data data
|
|
1292
|
+
send_datagram data, @return_address
|
|
1293
|
+
end
|
|
1294
|
+
end
|
|
1295
|
+
end
|
|
1296
|
+
|
|
1297
|
+
# load base EM api on top, now that we have the underlying pure ruby
|
|
1298
|
+
# implementation defined
|
|
1299
|
+
require 'eventmachine'
|