sonixlabs-eventmachine-java 1.0.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (178) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +23 -0
  3. data/.travis.yml +12 -0
  4. data/.yardopts +7 -0
  5. data/CHANGELOG.md +33 -0
  6. data/GNU +281 -0
  7. data/Gemfile +2 -0
  8. data/LICENSE +60 -0
  9. data/README.md +109 -0
  10. data/README_JP.md +18 -0
  11. data/Rakefile +20 -0
  12. data/docs/DocumentationGuidesIndex.md +27 -0
  13. data/docs/GettingStarted.md +521 -0
  14. data/docs/old/ChangeLog +211 -0
  15. data/docs/old/DEFERRABLES +246 -0
  16. data/docs/old/EPOLL +141 -0
  17. data/docs/old/INSTALL +13 -0
  18. data/docs/old/KEYBOARD +42 -0
  19. data/docs/old/LEGAL +25 -0
  20. data/docs/old/LIGHTWEIGHT_CONCURRENCY +130 -0
  21. data/docs/old/PURE_RUBY +75 -0
  22. data/docs/old/RELEASE_NOTES +94 -0
  23. data/docs/old/SMTP +4 -0
  24. data/docs/old/SPAWNED_PROCESSES +148 -0
  25. data/docs/old/TODO +8 -0
  26. data/eventmachine.gemspec +38 -0
  27. data/examples/guides/getting_started/01_eventmachine_echo_server.rb +18 -0
  28. data/examples/guides/getting_started/02_eventmachine_echo_server_that_recognizes_exit_command.rb +22 -0
  29. data/examples/guides/getting_started/03_simple_chat_server.rb +149 -0
  30. data/examples/guides/getting_started/04_simple_chat_server_step_one.rb +27 -0
  31. data/examples/guides/getting_started/05_simple_chat_server_step_two.rb +43 -0
  32. data/examples/guides/getting_started/06_simple_chat_server_step_three.rb +98 -0
  33. data/examples/guides/getting_started/07_simple_chat_server_step_four.rb +121 -0
  34. data/examples/guides/getting_started/08_simple_chat_server_step_five.rb +141 -0
  35. data/examples/old/ex_channel.rb +43 -0
  36. data/examples/old/ex_queue.rb +2 -0
  37. data/examples/old/ex_tick_loop_array.rb +15 -0
  38. data/examples/old/ex_tick_loop_counter.rb +32 -0
  39. data/examples/old/helper.rb +2 -0
  40. data/ext/binder.cpp +124 -0
  41. data/ext/binder.h +46 -0
  42. data/ext/cmain.cpp +887 -0
  43. data/ext/ed.cpp +1988 -0
  44. data/ext/ed.h +422 -0
  45. data/ext/em.cpp +2351 -0
  46. data/ext/em.h +244 -0
  47. data/ext/eventmachine.h +128 -0
  48. data/ext/extconf.rb +177 -0
  49. data/ext/fastfilereader/extconf.rb +103 -0
  50. data/ext/fastfilereader/mapper.cpp +214 -0
  51. data/ext/fastfilereader/mapper.h +59 -0
  52. data/ext/fastfilereader/rubymain.cpp +127 -0
  53. data/ext/kb.cpp +79 -0
  54. data/ext/page.cpp +107 -0
  55. data/ext/page.h +51 -0
  56. data/ext/pipe.cpp +347 -0
  57. data/ext/project.h +156 -0
  58. data/ext/rubymain.cpp +1318 -0
  59. data/ext/ssl.cpp +468 -0
  60. data/ext/ssl.h +94 -0
  61. data/java/.classpath +6 -0
  62. data/java/.gitignore +1 -0
  63. data/java/.project +17 -0
  64. data/java/src/com/rubyeventmachine/DatagramPacket.java +13 -0
  65. data/java/src/com/rubyeventmachine/EmReactor.java +529 -0
  66. data/java/src/com/rubyeventmachine/EmReactorException.java +40 -0
  67. data/java/src/com/rubyeventmachine/EventCallback.java +7 -0
  68. data/java/src/com/rubyeventmachine/EventCode.java +26 -0
  69. data/java/src/com/rubyeventmachine/EventableChannel.java +130 -0
  70. data/java/src/com/rubyeventmachine/EventableDatagramChannel.java +180 -0
  71. data/java/src/com/rubyeventmachine/EventableSocketChannel.java +405 -0
  72. data/java/src/com/rubyeventmachine/SslBox.java +310 -0
  73. data/lib/em/buftok.rb +110 -0
  74. data/lib/em/callback.rb +58 -0
  75. data/lib/em/channel.rb +64 -0
  76. data/lib/em/completion.rb +304 -0
  77. data/lib/em/connection.rb +712 -0
  78. data/lib/em/deferrable.rb +210 -0
  79. data/lib/em/deferrable/pool.rb +2 -0
  80. data/lib/em/file_watch.rb +73 -0
  81. data/lib/em/future.rb +61 -0
  82. data/lib/em/iterator.rb +231 -0
  83. data/lib/em/messages.rb +66 -0
  84. data/lib/em/pool.rb +151 -0
  85. data/lib/em/process_watch.rb +45 -0
  86. data/lib/em/processes.rb +123 -0
  87. data/lib/em/protocols.rb +37 -0
  88. data/lib/em/protocols/header_and_content.rb +138 -0
  89. data/lib/em/protocols/httpclient.rb +279 -0
  90. data/lib/em/protocols/httpclient2.rb +600 -0
  91. data/lib/em/protocols/line_and_text.rb +125 -0
  92. data/lib/em/protocols/line_protocol.rb +29 -0
  93. data/lib/em/protocols/linetext2.rb +161 -0
  94. data/lib/em/protocols/memcache.rb +331 -0
  95. data/lib/em/protocols/object_protocol.rb +46 -0
  96. data/lib/em/protocols/postgres3.rb +246 -0
  97. data/lib/em/protocols/saslauth.rb +175 -0
  98. data/lib/em/protocols/smtpclient.rb +365 -0
  99. data/lib/em/protocols/smtpserver.rb +643 -0
  100. data/lib/em/protocols/socks4.rb +66 -0
  101. data/lib/em/protocols/stomp.rb +205 -0
  102. data/lib/em/protocols/tcptest.rb +54 -0
  103. data/lib/em/pure_ruby.rb +1017 -0
  104. data/lib/em/queue.rb +71 -0
  105. data/lib/em/resolver.rb +192 -0
  106. data/lib/em/spawnable.rb +84 -0
  107. data/lib/em/streamer.rb +118 -0
  108. data/lib/em/threaded_resource.rb +90 -0
  109. data/lib/em/tick_loop.rb +85 -0
  110. data/lib/em/timers.rb +61 -0
  111. data/lib/em/version.rb +3 -0
  112. data/lib/eventmachine.rb +1553 -0
  113. data/lib/jeventmachine.rb +331 -0
  114. data/lib/sonixlabs-eventmachine-java.rb +1 -0
  115. data/rakelib/cpp.rake_example +77 -0
  116. data/rakelib/package.rake +96 -0
  117. data/rakelib/test.rake +8 -0
  118. data/tests/client.crt +31 -0
  119. data/tests/client.key +51 -0
  120. data/tests/em_test_helper.rb +64 -0
  121. data/tests/server.crt +36 -0
  122. data/tests/server.key +51 -0
  123. data/tests/test_attach.rb +150 -0
  124. data/tests/test_basic.rb +294 -0
  125. data/tests/test_channel.rb +62 -0
  126. data/tests/test_completion.rb +177 -0
  127. data/tests/test_connection_count.rb +53 -0
  128. data/tests/test_defer.rb +18 -0
  129. data/tests/test_deferrable.rb +35 -0
  130. data/tests/test_epoll.rb +145 -0
  131. data/tests/test_error_handler.rb +38 -0
  132. data/tests/test_exc.rb +28 -0
  133. data/tests/test_file_watch.rb +65 -0
  134. data/tests/test_futures.rb +170 -0
  135. data/tests/test_get_sock_opt.rb +37 -0
  136. data/tests/test_handler_check.rb +35 -0
  137. data/tests/test_hc.rb +155 -0
  138. data/tests/test_httpclient.rb +190 -0
  139. data/tests/test_httpclient2.rb +133 -0
  140. data/tests/test_idle_connection.rb +25 -0
  141. data/tests/test_inactivity_timeout.rb +54 -0
  142. data/tests/test_iterator.rb +97 -0
  143. data/tests/test_kb.rb +34 -0
  144. data/tests/test_line_protocol.rb +33 -0
  145. data/tests/test_ltp.rb +138 -0
  146. data/tests/test_ltp2.rb +288 -0
  147. data/tests/test_next_tick.rb +104 -0
  148. data/tests/test_object_protocol.rb +36 -0
  149. data/tests/test_pause.rb +102 -0
  150. data/tests/test_pending_connect_timeout.rb +52 -0
  151. data/tests/test_pool.rb +194 -0
  152. data/tests/test_process_watch.rb +48 -0
  153. data/tests/test_processes.rb +128 -0
  154. data/tests/test_proxy_connection.rb +180 -0
  155. data/tests/test_pure.rb +88 -0
  156. data/tests/test_queue.rb +50 -0
  157. data/tests/test_resolver.rb +55 -0
  158. data/tests/test_running.rb +14 -0
  159. data/tests/test_sasl.rb +47 -0
  160. data/tests/test_send_file.rb +217 -0
  161. data/tests/test_servers.rb +33 -0
  162. data/tests/test_set_sock_opt.rb +37 -0
  163. data/tests/test_shutdown_hooks.rb +23 -0
  164. data/tests/test_smtpclient.rb +55 -0
  165. data/tests/test_smtpserver.rb +57 -0
  166. data/tests/test_spawn.rb +293 -0
  167. data/tests/test_ssl_args.rb +78 -0
  168. data/tests/test_ssl_echo_data.rb +60 -0
  169. data/tests/test_ssl_methods.rb +56 -0
  170. data/tests/test_ssl_verify.rb +82 -0
  171. data/tests/test_stomp.rb +37 -0
  172. data/tests/test_system.rb +42 -0
  173. data/tests/test_threaded_resource.rb +53 -0
  174. data/tests/test_tick_loop.rb +59 -0
  175. data/tests/test_timers.rb +123 -0
  176. data/tests/test_ud.rb +8 -0
  177. data/tests/test_unbind_reason.rb +48 -0
  178. metadata +298 -0
@@ -0,0 +1,40 @@
1
+ /**
2
+ * $Id$
3
+ *
4
+ * Author:: Francis Cianfrocca (gmail: blackhedd)
5
+ * Homepage:: http://rubyeventmachine.com
6
+ * Date:: 15 Jul 2007
7
+ *
8
+ * See EventMachine and EventMachine::Connection for documentation and
9
+ * usage examples.
10
+ *
11
+ *
12
+ *----------------------------------------------------------------------------
13
+ *
14
+ * Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved.
15
+ * Gmail: blackhedd
16
+ *
17
+ * This program is free software; you can redistribute it and/or modify
18
+ * it under the terms of either: 1) the GNU General Public License
19
+ * as published by the Free Software Foundation; either version 2 of the
20
+ * License, or (at your option) any later version; or 2) Ruby's License.
21
+ *
22
+ * See the file COPYING for complete licensing information.
23
+ *
24
+ *---------------------------------------------------------------------------
25
+ *
26
+ *
27
+ */
28
+
29
+ package com.rubyeventmachine;
30
+
31
+ /**
32
+ * @author francis
33
+ *
34
+ */
35
+ public class EmReactorException extends Exception {
36
+ static final long serialVersionUID = 0;
37
+ public EmReactorException (String msg) {
38
+ super (msg);
39
+ }
40
+ }
@@ -0,0 +1,7 @@
1
+ package com.rubyeventmachine;
2
+
3
+ import java.nio.ByteBuffer;
4
+
5
+ public interface EventCallback {
6
+ void trigger(long sig, EventCode eventType, ByteBuffer data, long data2);
7
+ }
@@ -0,0 +1,26 @@
1
+ package com.rubyeventmachine;
2
+
3
+ public enum EventCode {
4
+ EM_TIMER_FIRED(100),
5
+ EM_CONNECTION_READ(101),
6
+ EM_CONNECTION_UNBOUND(102),
7
+ EM_CONNECTION_ACCEPTED(103),
8
+ EM_CONNECTION_COMPLETED(104),
9
+ EM_LOOPBREAK_SIGNAL(105),
10
+ EM_CONNECTION_NOTIFY_READABLE(106),
11
+ EM_CONNECTION_NOTIFY_WRITABLE(107),
12
+ EM_SSL_HANDSHAKE_COMPLETED(108),
13
+ EM_SSL_VERIFY(109),
14
+ EM_PROXY_TARGET_UNBOUND(110),
15
+ EM_PROXY_COMPLETED(111);
16
+
17
+ private final int intValue;
18
+
19
+ EventCode(int intValue) {
20
+ this.intValue = intValue;
21
+ }
22
+
23
+ public int intValue() {
24
+ return intValue;
25
+ }
26
+ }
@@ -0,0 +1,130 @@
1
+ /**
2
+ * $Id$
3
+ *
4
+ * Author:: Francis Cianfrocca (gmail: blackhedd)
5
+ * Homepage:: http://rubyeventmachine.com
6
+ * Date:: 15 Jul 2007
7
+ *
8
+ * See EventMachine and EventMachine::Connection for documentation and
9
+ * usage examples.
10
+ *
11
+ *
12
+ *----------------------------------------------------------------------------
13
+ *
14
+ * Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved.
15
+ * Gmail: blackhedd
16
+ *
17
+ * This program is free software; you can redistribute it and/or modify
18
+ * it under the terms of either: 1) the GNU General Public License
19
+ * as published by the Free Software Foundation; either version 2 of the
20
+ * License, or (at your option) any later version; or 2) Ruby's License.
21
+ *
22
+ * See the file COPYING for complete licensing information.
23
+ *
24
+ *---------------------------------------------------------------------------
25
+ *
26
+ *
27
+ */
28
+
29
+ package com.rubyeventmachine;
30
+
31
+ import java.nio.ByteBuffer;
32
+ import java.io.IOException;
33
+ import java.nio.channels.ClosedChannelException;
34
+ import java.nio.channels.Selector;
35
+ import java.util.LinkedList;
36
+
37
+ public abstract class EventableChannel<OutboundPacketType> {
38
+ protected final long binding;
39
+ protected final Selector selector;
40
+ protected final LinkedList<OutboundPacketType> outboundQ;
41
+ protected final EventCallback callback;
42
+ private final ByteBuffer readBuffer;
43
+
44
+ public EventableChannel(long binding, Selector selector,
45
+ EventCallback callback) {
46
+ this.binding = binding;
47
+ this.selector = selector;
48
+ this.callback = callback;
49
+ this.outboundQ = new LinkedList<OutboundPacketType>();
50
+ this.readBuffer = ByteBuffer.allocate(32*1024); // don't use a direct buffer. Ruby doesn't seem to like them.
51
+ }
52
+
53
+ public abstract void scheduleOutboundData(ByteBuffer bb);
54
+
55
+ public abstract void scheduleOutboundDatagram(ByteBuffer bb,
56
+ String recipAddress, int recipPort);
57
+
58
+ public abstract boolean scheduleClose(boolean afterWriting);
59
+
60
+ public abstract void startTls();
61
+
62
+ public long getBinding() {
63
+ return binding;
64
+ }
65
+
66
+ public abstract void register() throws ClosedChannelException;
67
+
68
+ /**
69
+ * This is called by the reactor after it finishes running. The idea is to
70
+ * free network resources.
71
+ */
72
+ public abstract void close();
73
+
74
+ public void setCommInactivityTimeout (long seconds) {
75
+ // TODO
76
+ System.out.println ("SET COMM INACTIVITY UNIMPLEMENTED IN JRUBY" + seconds);
77
+ }
78
+
79
+ public abstract Object[] getPeerName();
80
+
81
+ public abstract Object[] getSockName();
82
+
83
+ public abstract boolean isWatchOnly();
84
+
85
+ public abstract boolean isNotifyReadable();
86
+
87
+ public abstract boolean isNotifyWritable();
88
+
89
+ protected abstract boolean handshakeNeeded();
90
+ protected abstract boolean performHandshake();
91
+
92
+ protected abstract void readInboundData(ByteBuffer dst) throws IOException;
93
+
94
+ public boolean read() {
95
+ if (handshakeNeeded()) {
96
+ return performHandshake();
97
+ } else if (isWatchOnly() && isNotifyReadable()) {
98
+ callback.trigger(binding, EventCode.EM_CONNECTION_NOTIFY_READABLE, null, 0);
99
+ } else {
100
+ readBuffer.clear();
101
+
102
+ try {
103
+ readInboundData(readBuffer);
104
+ readBuffer.flip();
105
+ if (readBuffer.limit() > 0)
106
+ callback.trigger(binding, EventCode.EM_CONNECTION_READ, readBuffer, 0);
107
+ } catch (IOException e) {
108
+ return false;
109
+ }
110
+ }
111
+ return true;
112
+ }
113
+
114
+ protected abstract boolean writeOutboundData() throws IOException;
115
+
116
+ public boolean write() {
117
+ if (handshakeNeeded()) {
118
+ return performHandshake();
119
+ } else if (isWatchOnly() || isNotifyWritable()) {
120
+ callback.trigger(binding, EventCode.EM_CONNECTION_NOTIFY_WRITABLE, null, 0);
121
+ } else {
122
+ try {
123
+ return writeOutboundData();
124
+ } catch (IOException e) {
125
+ return false;
126
+ }
127
+ }
128
+ return true;
129
+ }
130
+ }
@@ -0,0 +1,180 @@
1
+ /**
2
+ * $Id$
3
+ *
4
+ * Author:: Francis Cianfrocca (gmail: blackhedd)
5
+ * Homepage:: http://rubyeventmachine.com
6
+ * Date:: 15 Jul 2007
7
+ *
8
+ * See EventMachine and EventMachine::Connection for documentation and
9
+ * usage examples.
10
+ *
11
+ *
12
+ *----------------------------------------------------------------------------
13
+ *
14
+ * Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved.
15
+ * Gmail: blackhedd
16
+ *
17
+ * This program is free software; you can redistribute it and/or modify
18
+ * it under the terms of either: 1) the GNU General Public License
19
+ * as published by the Free Software Foundation; either version 2 of the
20
+ * License, or (at your option) any later version; or 2) Ruby's License.
21
+ *
22
+ * See the file COPYING for complete licensing information.
23
+ *
24
+ *---------------------------------------------------------------------------
25
+ *
26
+ *
27
+ */
28
+
29
+
30
+ package com.rubyeventmachine;
31
+
32
+ import java.nio.ByteBuffer;
33
+ import java.nio.channels.ClosedChannelException;
34
+ import java.nio.channels.SelectionKey;
35
+ import java.nio.channels.Selector;
36
+ import java.nio.channels.DatagramChannel;
37
+ import java.io.*;
38
+ import java.net.*;
39
+
40
+ public class EventableDatagramChannel extends EventableChannel<DatagramPacket> {
41
+
42
+ DatagramChannel channel;
43
+ boolean bCloseScheduled;
44
+ SocketAddress returnAddress;
45
+
46
+ public EventableDatagramChannel (DatagramChannel dc, long _binding, Selector sel, EventCallback callback) throws ClosedChannelException {
47
+ super(_binding, sel, callback);
48
+ channel = dc;
49
+ bCloseScheduled = false;
50
+
51
+ dc.register(selector, SelectionKey.OP_READ, this);
52
+ }
53
+
54
+ public void scheduleOutboundData (ByteBuffer bb) {
55
+ try {
56
+ if ((!bCloseScheduled) && (bb.remaining() > 0)) {
57
+ outboundQ.addLast(new DatagramPacket(bb, returnAddress));
58
+ channel.register(selector, SelectionKey.OP_WRITE | SelectionKey.OP_READ, this);
59
+ }
60
+ } catch (ClosedChannelException e) {
61
+ throw new RuntimeException ("no outbound data");
62
+ }
63
+ }
64
+
65
+ public void scheduleOutboundDatagram (ByteBuffer bb, String recipAddress, int recipPort) {
66
+ try {
67
+ if ((!bCloseScheduled) && (bb.remaining() > 0)) {
68
+ outboundQ.addLast(new DatagramPacket (bb, new InetSocketAddress (recipAddress, recipPort)));
69
+ channel.register(selector, SelectionKey.OP_WRITE | SelectionKey.OP_READ, this);
70
+ }
71
+ } catch (ClosedChannelException e) {
72
+ throw new RuntimeException ("no outbound data");
73
+ }
74
+ }
75
+
76
+ public boolean scheduleClose (boolean afterWriting) {
77
+ System.out.println ("NOT SCHEDULING CLOSE ON DATAGRAM");
78
+ return false;
79
+ }
80
+
81
+ public void startTls() {
82
+ throw new RuntimeException ("TLS is unimplemented on this Channel");
83
+ }
84
+
85
+ public void register() throws ClosedChannelException {
86
+ // TODO
87
+ }
88
+
89
+ /**
90
+ * Terminate with extreme prejudice. Don't assume there will be another pass through
91
+ * the reactor core.
92
+ */
93
+ public void close() {
94
+ try {
95
+ channel.close();
96
+ } catch (IOException e) {
97
+ }
98
+ }
99
+
100
+ public void readInboundData (ByteBuffer dst) {
101
+ returnAddress = null;
102
+ try {
103
+ // If there is no datagram available (we're nonblocking after all),
104
+ // then channel.receive returns null.
105
+ returnAddress = channel.receive(dst);
106
+ } catch (IOException e) {
107
+ // probably a no-op. The caller will see the empty (or even partial) buffer
108
+ // and presumably do the right thing.
109
+ }
110
+ }
111
+
112
+ protected boolean writeOutboundData() {
113
+ while (!outboundQ.isEmpty()) {
114
+ DatagramPacket p = outboundQ.getFirst();
115
+ int written = 0;
116
+ try {
117
+ // With a datagram socket, it's ok to send an empty buffer.
118
+ written = channel.send(p.bb, p.recipient);
119
+ }
120
+ catch (IOException e) {
121
+ return false;
122
+ }
123
+
124
+ /* Did we consume the whole outbound buffer? If yes, pop it off and
125
+ * keep looping. If no, the outbound network buffers are full, so break
126
+ * out of here. There's a flaw that affects outbound buffers that are intentionally
127
+ * empty. We can tell whether they got sent or not. So we assume they were.
128
+ * TODO: As implemented, this ALWAYS discards packets if they were at least
129
+ * partially written. This matches the behavior of the C++ EM. My judgment
130
+ * is that this is less surprising than fragmenting the data and sending multiple
131
+ * packets would be. I could be wrong, so this is subject to change.
132
+ */
133
+
134
+ if ((written > 0) || (p.bb.remaining() == 0))
135
+ outboundQ.removeFirst();
136
+ else
137
+ break;
138
+ }
139
+
140
+ if (outboundQ.isEmpty()) {
141
+ try {
142
+ channel.register(selector, SelectionKey.OP_READ, this);
143
+ } catch (ClosedChannelException e) {}
144
+ }
145
+
146
+ // ALWAYS drain the outbound queue before triggering a connection close.
147
+ // If anyone wants to close immediately, they're responsible for clearing
148
+ // the outbound queue.
149
+ return (bCloseScheduled && outboundQ.isEmpty()) ? false : true;
150
+ }
151
+
152
+ public Object[] getPeerName () {
153
+ if (returnAddress != null) {
154
+ InetSocketAddress inetAddr = (InetSocketAddress) returnAddress;
155
+ return new Object[]{ inetAddr.getPort(), inetAddr.getHostName() };
156
+ } else {
157
+ return null;
158
+ }
159
+ }
160
+
161
+ public Object[] getSockName () {
162
+ DatagramSocket socket = channel.socket();
163
+ return new Object[]{ socket.getLocalPort(),
164
+ socket.getLocalAddress().getHostAddress() };
165
+ }
166
+
167
+ public boolean isWatchOnly() { return false; }
168
+ public boolean isNotifyReadable() { return false; }
169
+ public boolean isNotifyWritable() { return false; }
170
+
171
+ @Override
172
+ protected boolean handshakeNeeded() {
173
+ return false;
174
+ }
175
+
176
+ @Override
177
+ protected boolean performHandshake() {
178
+ return true;
179
+ }
180
+ }
@@ -0,0 +1,405 @@
1
+ /**
2
+ * $Id$
3
+ *
4
+ * Author:: Francis Cianfrocca (gmail: blackhedd)
5
+ * Homepage:: http://rubyeventmachine.com
6
+ * Date:: 15 Jul 2007
7
+ *
8
+ * See EventMachine and EventMachine::Connection for documentation and
9
+ * usage examples.
10
+ *
11
+ *
12
+ *----------------------------------------------------------------------------
13
+ *
14
+ * Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved.
15
+ * Gmail: blackhedd
16
+ *
17
+ * This program is free software; you can redistribute it and/or modify
18
+ * it under the terms of either: 1) the GNU General Public License
19
+ * as published by the Free Software Foundation; either version 2 of the
20
+ * License, or (at your option) any later version; or 2) Ruby's License.
21
+ *
22
+ * See the file COPYING for complete licensing information.
23
+ *
24
+ *---------------------------------------------------------------------------
25
+ *
26
+ *
27
+ */
28
+
29
+ /**
30
+ *
31
+ */
32
+ package com.rubyeventmachine;
33
+
34
+ /**
35
+ * @author francis
36
+ *
37
+ */
38
+
39
+ import java.io.FileDescriptor;
40
+ import java.io.IOException;
41
+ import java.lang.reflect.Field;
42
+ import java.net.Socket;
43
+ import java.nio.ByteBuffer;
44
+ import java.nio.channels.ClosedChannelException;
45
+ import java.nio.channels.SelectionKey;
46
+ import java.nio.channels.Selector;
47
+ import java.nio.channels.SocketChannel;
48
+ import java.security.KeyStore;
49
+ import java.security.cert.CertificateException;
50
+ import java.security.cert.X509Certificate;
51
+
52
+ import javax.net.ssl.X509TrustManager;
53
+
54
+ public class EventableSocketChannel extends EventableChannel<ByteBuffer> {
55
+ SelectionKey channelKey;
56
+ SocketChannel channel;
57
+
58
+ boolean bCloseScheduled;
59
+ boolean bConnectPending;
60
+ boolean bWatchOnly;
61
+ boolean bAttached;
62
+ boolean bNotifyReadable;
63
+ boolean bNotifyWritable;
64
+
65
+ SslBox sslBox;
66
+ private KeyStore keyStore;
67
+ private boolean verifyPeer;
68
+ private boolean bIsServer;
69
+ private boolean shouldAcceptSslPeer = false;
70
+
71
+ public EventableSocketChannel (SocketChannel sc, long _binding, Selector sel, EventCallback callback) {
72
+ super(_binding, sel, callback);
73
+ channel = sc;
74
+ bCloseScheduled = false;
75
+ bConnectPending = false;
76
+ bWatchOnly = false;
77
+ bAttached = false;
78
+ bNotifyReadable = false;
79
+ bNotifyWritable = false;
80
+ bIsServer = false;
81
+ }
82
+
83
+ public SocketChannel getChannel() {
84
+ return channel;
85
+ }
86
+
87
+ public void register() throws ClosedChannelException {
88
+ if (channelKey == null) {
89
+ int events = currentEvents();
90
+ channelKey = channel.register(selector, events, this);
91
+ }
92
+ }
93
+
94
+ /**
95
+ * Terminate with extreme prejudice. Don't assume there will be another pass through
96
+ * the reactor core.
97
+ */
98
+ public void close() {
99
+ if (channelKey != null) {
100
+ channelKey.cancel();
101
+ channelKey = null;
102
+ }
103
+
104
+ if (bAttached) {
105
+ // attached channels are copies, so reset the file descriptor to prevent java from close()ing it
106
+ Field f;
107
+ FileDescriptor fd;
108
+
109
+ try {
110
+ /* do _NOT_ clobber fdVal here, it will break epoll/kqueue on jdk6!
111
+ * channelKey.cancel() above does not occur until the next call to select
112
+ * and if fdVal is gone, we will continue to get events for this fd.
113
+ *
114
+ * instead, remove fdVal in cleanup(), which is processed via DetachedConnections,
115
+ * after UnboundConnections but before NewConnections.
116
+ */
117
+
118
+ f = channel.getClass().getDeclaredField("fd");
119
+ f.setAccessible(true);
120
+ fd = (FileDescriptor) f.get(channel);
121
+
122
+ f = fd.getClass().getDeclaredField("fd");
123
+ f.setAccessible(true);
124
+ f.set(fd, -1);
125
+ } catch (java.lang.NoSuchFieldException e) {
126
+ e.printStackTrace();
127
+ } catch (java.lang.IllegalAccessException e) {
128
+ e.printStackTrace();
129
+ }
130
+
131
+ return;
132
+ }
133
+
134
+ try {
135
+ channel.close();
136
+ } catch (IOException e) {
137
+ }
138
+ }
139
+
140
+ public void cleanup() {
141
+ if (bAttached) {
142
+ Field f;
143
+ try {
144
+ f = channel.getClass().getDeclaredField("fdVal");
145
+ f.setAccessible(true);
146
+ f.set(channel, -1);
147
+ } catch (java.lang.NoSuchFieldException e) {
148
+ e.printStackTrace();
149
+ } catch (java.lang.IllegalAccessException e) {
150
+ e.printStackTrace();
151
+ }
152
+ }
153
+
154
+ channel = null;
155
+ }
156
+
157
+ public void scheduleOutboundData (ByteBuffer bb) {
158
+ if (!bCloseScheduled && bb.remaining() > 0) {
159
+ outboundQ.addLast( bb );
160
+ updateEvents();
161
+ }
162
+ }
163
+
164
+ public void scheduleOutboundDatagram (ByteBuffer bb, String recipAddress, int recipPort) {
165
+ throw new RuntimeException ("datagram sends not supported on this channel");
166
+ }
167
+
168
+ /**
169
+ * Called by the reactor when we have selected readable.
170
+ */
171
+ public void readInboundData (ByteBuffer bb) throws IOException {
172
+ int bytesRead = (sslBox != null) ? sslBox.read(bb) : channel.read(bb);
173
+ if (bytesRead == -1)
174
+ throw new IOException ("eof");
175
+ }
176
+
177
+ /**
178
+ * Called by the reactor when we have selected writable.
179
+ * Return false to indicate an error that should cause the connection to close.
180
+ * TODO, VERY IMPORTANT: we're here because we selected writable, but it's always
181
+ * possible to become unwritable between the poll and when we get here. The way
182
+ * this code is written, we're depending on a nonblocking write NOT TO CONSUME
183
+ * the whole outbound buffer in this case, rather than firing an exception.
184
+ * We should somehow verify that this is indeed Java's defined behavior.
185
+ * Also TODO, see if we can use gather I/O rather than one write at a time.
186
+ * Ought to be a big performance enhancer.
187
+ * @return
188
+ */
189
+ protected boolean writeOutboundData() throws IOException {
190
+ while (!outboundQ.isEmpty()) {
191
+ ByteBuffer b = outboundQ.getFirst();
192
+ if (b.remaining() > 0) {
193
+ if (sslBox != null)
194
+ sslBox.write(b);
195
+ else
196
+ channel.write(b);
197
+ }
198
+
199
+ // Did we consume the whole outbound buffer? If yes,
200
+ // pop it off and keep looping. If no, the outbound network
201
+ // buffers are full, so break out of here.
202
+ if (b.remaining() == 0)
203
+ outboundQ.removeFirst();
204
+ else
205
+ break;
206
+ }
207
+
208
+ if (outboundQ.isEmpty() && !bCloseScheduled) {
209
+ updateEvents();
210
+ }
211
+
212
+ // ALWAYS drain the outbound queue before triggering a connection close.
213
+ // If anyone wants to close immediately, they're responsible for clearing
214
+ // the outbound queue.
215
+ return (bCloseScheduled && outboundQ.isEmpty()) ? false : true;
216
+ }
217
+
218
+ public void setConnectPending() {
219
+ bConnectPending = true;
220
+ updateEvents();
221
+ }
222
+
223
+ /**
224
+ * Called by the reactor when we have selected connectable.
225
+ * Return false to indicate an error that should cause the connection to close.
226
+ */
227
+ public boolean finishConnecting() {
228
+ try {
229
+ channel.finishConnect();
230
+ bConnectPending = false;
231
+ updateEvents();
232
+ callback.trigger(binding, EventCode.EM_CONNECTION_COMPLETED, null, 0);
233
+ return true;
234
+ } catch (IOException e) {
235
+ return false;
236
+ }
237
+ }
238
+
239
+ public boolean scheduleClose (boolean afterWriting) {
240
+ // TODO: What the hell happens here if bConnectPending is set?
241
+ if (!afterWriting)
242
+ outboundQ.clear();
243
+
244
+ if (outboundQ.isEmpty())
245
+ return true;
246
+ else {
247
+ updateEvents();
248
+ bCloseScheduled = true;
249
+ return false;
250
+ }
251
+ }
252
+
253
+ public void setTlsParms(KeyStore keyStore, boolean verifyPeer) {
254
+ this.keyStore = keyStore;
255
+ this.verifyPeer = verifyPeer;
256
+ }
257
+
258
+ public void startTls() {
259
+ if (sslBox == null) {
260
+ Object[] peerName = getPeerName();
261
+ int port = (Integer) peerName[0];
262
+ String host = (String) peerName[1];
263
+ X509TrustManager tm = new CallbackBasedTrustManager();
264
+ sslBox = new SslBox(bIsServer, channel, keyStore, tm, verifyPeer, host, port);
265
+ updateEvents();
266
+ }
267
+ }
268
+
269
+ public Object[] getPeerName () {
270
+ Socket sock = channel.socket();
271
+ return new Object[]{ sock.getPort(), sock.getInetAddress().getHostAddress() };
272
+ }
273
+
274
+ public Object[] getSockName () {
275
+ Socket sock = channel.socket();
276
+ return new Object[]{ sock.getLocalPort(),
277
+ sock.getLocalAddress().getHostAddress() };
278
+ }
279
+
280
+ public void setWatchOnly() {
281
+ bWatchOnly = true;
282
+ updateEvents();
283
+ }
284
+ public boolean isWatchOnly() { return bWatchOnly; }
285
+
286
+ public void setAttached() {
287
+ bAttached = true;
288
+ }
289
+ public boolean isAttached() { return bAttached; }
290
+
291
+ public void setNotifyReadable (boolean mode) {
292
+ bNotifyReadable = mode;
293
+ updateEvents();
294
+ }
295
+ public boolean isNotifyReadable() { return bNotifyReadable; }
296
+
297
+ public void setNotifyWritable (boolean mode) {
298
+ bNotifyWritable = mode;
299
+ updateEvents();
300
+ }
301
+ public boolean isNotifyWritable() { return bNotifyWritable; }
302
+
303
+ private void updateEvents() {
304
+ if (channelKey == null)
305
+ return;
306
+
307
+ int events = currentEvents();
308
+
309
+ if (channelKey.interestOps() != events) {
310
+ channelKey.interestOps(events);
311
+ }
312
+ }
313
+
314
+ private int currentEvents() {
315
+ int events = 0;
316
+
317
+ if (bWatchOnly)
318
+ {
319
+ if (bNotifyReadable)
320
+ events |= SelectionKey.OP_READ;
321
+
322
+ if (bNotifyWritable)
323
+ events |= SelectionKey.OP_WRITE;
324
+ }
325
+ else
326
+ {
327
+ if (bConnectPending)
328
+ events |= SelectionKey.OP_CONNECT;
329
+ else {
330
+ events |= SelectionKey.OP_READ;
331
+
332
+ if (!outboundQ.isEmpty() || (sslBox != null && sslBox.handshakeNeeded()))
333
+ events |= SelectionKey.OP_WRITE;
334
+ }
335
+ }
336
+
337
+ return events;
338
+ }
339
+
340
+ public void setServerMode() {
341
+ bIsServer = true;
342
+ }
343
+
344
+ @Override
345
+ protected boolean handshakeNeeded() {
346
+ return sslBox != null && sslBox.handshakeNeeded();
347
+ }
348
+
349
+ @Override
350
+ protected boolean performHandshake() {
351
+ if (sslBox == null) return true;
352
+
353
+ if (sslBox.handshake(channelKey)) {
354
+ if (!sslBox.handshakeNeeded()) {
355
+ callback.trigger(binding, EventCode.EM_SSL_HANDSHAKE_COMPLETED, null, 0);
356
+ updateEvents();
357
+ }
358
+ return true;
359
+ }
360
+ return false;
361
+ }
362
+
363
+ public void acceptSslPeer() {
364
+ this.shouldAcceptSslPeer = true;
365
+ }
366
+
367
+ public class CallbackBasedTrustManager implements X509TrustManager {
368
+ public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
369
+ if (verifyPeer) fireEvent(chain[0]);
370
+ }
371
+
372
+ public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
373
+ if (verifyPeer) fireEvent(chain[0]);
374
+ }
375
+
376
+ public X509Certificate[] getAcceptedIssuers() {
377
+ return new X509Certificate[0];
378
+ }
379
+
380
+ private void fireEvent(X509Certificate cert) throws CertificateException {
381
+
382
+ ByteBuffer data = ByteBuffer.wrap(cert.getEncoded());
383
+
384
+ callback.trigger(binding, EventCode.EM_SSL_VERIFY, data, 0);
385
+
386
+ // If we should accept, the trigger will ultimately call our acceptSslPeer method.
387
+ if (! shouldAcceptSslPeer) {
388
+ throw new CertificateException("JRuby trigger was not fired");
389
+ }
390
+ }
391
+ }
392
+
393
+ public byte[] getPeerCert() {
394
+ if (sslBox != null) {
395
+ try {
396
+ javax.security.cert.X509Certificate peerCert = sslBox.getPeerCert();
397
+ return peerCert.getEncoded();
398
+ } catch (Exception e) {
399
+ }
400
+ }
401
+ return null;
402
+ }
403
+
404
+
405
+ }