eventmachine 1.0.5 → 1.0.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +12 -0
- data/README.md +1 -2
- data/ext/em.cpp +8 -3
- data/ext/extconf.rb +3 -0
- data/ext/fastfilereader/extconf.rb +3 -0
- data/ext/rubymain.cpp +15 -1
- data/java/src/com/rubyeventmachine/EmReactor.java +16 -0
- data/java/src/com/rubyeventmachine/EventableChannel.java +2 -0
- data/java/src/com/rubyeventmachine/EventableDatagramChannel.java +6 -0
- data/java/src/com/rubyeventmachine/EventableSocketChannel.java +55 -10
- data/lib/em/buftok.rb +34 -85
- data/lib/em/protocols/httpclient.rb +29 -11
- data/lib/em/protocols/line_and_text.rb +2 -3
- data/lib/em/protocols/linetext2.rb +0 -1
- data/lib/em/protocols/smtpserver.rb +26 -7
- data/lib/em/pure_ruby.rb +2 -2
- data/lib/em/version.rb +1 -1
- data/lib/jeventmachine.rb +17 -0
- data/tests/em_test_helper.rb +4 -0
- data/tests/test_basic.rb +1 -0
- data/tests/test_connection_write.rb +35 -0
- data/tests/test_httpclient.rb +43 -0
- data/tests/test_pause.rb +7 -2
- data/tests/test_process_watch.rb +1 -0
- data/tests/test_ssl_methods.rb +1 -0
- data/tests/test_ssl_verify.rb +2 -0
- metadata +27 -59
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: ddb654693896bb35c217860d917da4022c61d651
|
4
|
+
data.tar.gz: 36c8bdad2ee72ca20fb91dfecaeb50a99754935d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 7b05a2b6ad9ffa354c4f59959b7d3e4a9185988d47e5bbf0cd82452de351ac2c84936c5638b6cd8b5b841b350eef427bcb0c57f3014c6c4794ed257fdf9db342
|
7
|
+
data.tar.gz: dd8e19c10bfdf66ab05027e577558dcb3867e941b0148dfba96997c892222f3958beb4424454d9ca9cc17010954c0d5d9bc1ce0326a9f13acf861bc2ba8ac7f4
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,17 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## 1.0.6 (February 3, 2015)
|
4
|
+
* add support for Rubinius Process::Status [#568]
|
5
|
+
* small bugfixes for SmtpServer [#449]
|
6
|
+
* update buftok.rb [#547]
|
7
|
+
* fix assertion on Write() [#525]
|
8
|
+
* work around mkmf.rb bug preventing gem installation [#574]
|
9
|
+
* add pause/resume support to jruby reactor [#556]
|
10
|
+
* fix pure ruby reactor to use 127.0.0.1 instead of localhost [#439]
|
11
|
+
* fix compilation under macruby [#243]
|
12
|
+
* add chunked encoding to http client [#111]
|
13
|
+
* fix errors on win32 when dealing with pipes [1ea45498] [#105]
|
14
|
+
|
3
15
|
## 1.0.5 (February 2, 2015)
|
4
16
|
* use monotonic clocks on Linux, OS X, Solaris, and Windows [#563]
|
5
17
|
* use the rb_fd_* API to get autosized fd_sets [#502]
|
data/README.md
CHANGED
@@ -105,5 +105,4 @@ Copyright: (C) 2006-07 by Francis Cianfrocca. All Rights Reserved.
|
|
105
105
|
|
106
106
|
## Alternatives ##
|
107
107
|
|
108
|
-
If you are unhappy with EventMachine and want to use Ruby, check out [
|
109
|
-
One caveat: by May 2011, it did not support JRuby and Windows.
|
108
|
+
If you are unhappy with EventMachine and want to use Ruby, check out [Celluloid](https://celluloid.io/).
|
data/ext/em.cpp
CHANGED
@@ -938,7 +938,8 @@ void EventMachine_t::_RunSelectOnce()
|
|
938
938
|
/* 21Sep09: on windows, a non-blocking connect() that fails does not come up as writable.
|
939
939
|
Instead, it is added to the error set. See http://www.mail-archive.com/openssl-users@openssl.org/msg58500.html
|
940
940
|
*/
|
941
|
-
|
941
|
+
if (ed->IsConnectPending())
|
942
|
+
rb_fd_set (sd, &(SelectData.fderrors));
|
942
943
|
#endif
|
943
944
|
|
944
945
|
if (SelectData.maxsocket < sd)
|
@@ -973,8 +974,12 @@ void EventMachine_t::_RunSelectOnce()
|
|
973
974
|
continue;
|
974
975
|
assert (sd != INVALID_SOCKET);
|
975
976
|
|
976
|
-
if (rb_fd_isset (sd, &(SelectData.fdwrites)))
|
977
|
-
|
977
|
+
if (rb_fd_isset (sd, &(SelectData.fdwrites))) {
|
978
|
+
// Double-check SelectForWrite() still returns true. If not, one of the callbacks must have
|
979
|
+
// modified some value since we checked SelectForWrite() earlier in this method.
|
980
|
+
if (ed->SelectForWrite())
|
981
|
+
ed->Write();
|
982
|
+
}
|
978
983
|
if (rb_fd_isset (sd, &(SelectData.fdreads)))
|
979
984
|
ed->Read();
|
980
985
|
if (rb_fd_isset (sd, &(SelectData.fderrors)))
|
data/ext/extconf.rb
CHANGED
@@ -39,6 +39,9 @@ def manual_ssl_config
|
|
39
39
|
check_libs(libs) and check_heads(heads)
|
40
40
|
end
|
41
41
|
|
42
|
+
# Eager check devs tools
|
43
|
+
have_devel? if respond_to?(:have_devel?)
|
44
|
+
|
42
45
|
if ENV['CROSS_COMPILING']
|
43
46
|
openssl_version = ENV.fetch("OPENSSL_VERSION", "1.0.1i")
|
44
47
|
openssl_dir = File.expand_path("~/.rake-compiler/builds/openssl-#{openssl_version}/")
|
data/ext/rubymain.cpp
CHANGED
@@ -408,8 +408,22 @@ static VALUE t_get_subprocess_status (VALUE self, VALUE signature)
|
|
408
408
|
if (evma_get_subprocess_status (NUM2ULONG (signature), &status)) {
|
409
409
|
if (evma_get_subprocess_pid (NUM2ULONG (signature), &pid)) {
|
410
410
|
proc_status = rb_obj_alloc(rb_cProcStatus);
|
411
|
+
|
412
|
+
/* MRI Ruby uses hidden instance vars */
|
411
413
|
rb_iv_set(proc_status, "status", INT2FIX(status));
|
412
414
|
rb_iv_set(proc_status, "pid", INT2FIX(pid));
|
415
|
+
|
416
|
+
#ifdef RUBINIUS
|
417
|
+
/* Rubinius uses standard instance vars */
|
418
|
+
rb_iv_set(proc_status, "@pid", INT2FIX(pid));
|
419
|
+
if (WIFEXITED(status)) {
|
420
|
+
rb_iv_set(proc_status, "@status", INT2FIX(WEXITSTATUS(status)));
|
421
|
+
} else if(WIFSIGNALED(status)) {
|
422
|
+
rb_iv_set(proc_status, "@termsig", INT2FIX(WTERMSIG(status)));
|
423
|
+
} else if(WIFSTOPPED(status)){
|
424
|
+
rb_iv_set(proc_status, "@stopsig", INT2FIX(WSTOPSIG(status)));
|
425
|
+
}
|
426
|
+
#endif
|
413
427
|
}
|
414
428
|
}
|
415
429
|
|
@@ -611,7 +625,7 @@ static VALUE t_set_sock_opt (VALUE self, VALUE signature, VALUE lev, VALUE optna
|
|
611
625
|
int fd = evma_get_file_descriptor (NUM2ULONG (signature));
|
612
626
|
int level = NUM2INT(lev), option = NUM2INT(optname);
|
613
627
|
int i;
|
614
|
-
void *v;
|
628
|
+
const void *v;
|
615
629
|
socklen_t len;
|
616
630
|
|
617
631
|
switch (TYPE(optval)) {
|
@@ -569,6 +569,22 @@ public class EmReactor {
|
|
569
569
|
return Connections.get(sig).isNotifyWritable();
|
570
570
|
}
|
571
571
|
|
572
|
+
public boolean pauseConnection (long sig) {
|
573
|
+
return ((EventableSocketChannel) Connections.get(sig)).pause();
|
574
|
+
}
|
575
|
+
|
576
|
+
public boolean resumeConnection (long sig) {
|
577
|
+
return ((EventableSocketChannel) Connections.get(sig)).resume();
|
578
|
+
}
|
579
|
+
|
580
|
+
public boolean isConnectionPaused (long sig) {
|
581
|
+
return ((EventableSocketChannel) Connections.get(sig)).isPaused();
|
582
|
+
}
|
583
|
+
|
584
|
+
public long getOutboundDataSize (long sig) {
|
585
|
+
return Connections.get(sig).getOutboundDataSize();
|
586
|
+
}
|
587
|
+
|
572
588
|
public int getConnectionCount() {
|
573
589
|
return Connections.size() + Acceptors.size();
|
574
590
|
}
|
@@ -54,6 +54,7 @@ public class EventableDatagramChannel implements EventableChannel {
|
|
54
54
|
Selector selector;
|
55
55
|
boolean bCloseScheduled;
|
56
56
|
LinkedList<Packet> outboundQ;
|
57
|
+
long outboundS;
|
57
58
|
SocketAddress returnAddress;
|
58
59
|
|
59
60
|
|
@@ -63,6 +64,7 @@ public class EventableDatagramChannel implements EventableChannel {
|
|
63
64
|
selector = sel;
|
64
65
|
bCloseScheduled = false;
|
65
66
|
outboundQ = new LinkedList<Packet>();
|
67
|
+
outboundS = 0;
|
66
68
|
|
67
69
|
dc.register(selector, SelectionKey.OP_READ, this);
|
68
70
|
}
|
@@ -71,6 +73,7 @@ public class EventableDatagramChannel implements EventableChannel {
|
|
71
73
|
try {
|
72
74
|
if ((!bCloseScheduled) && (bb.remaining() > 0)) {
|
73
75
|
outboundQ.addLast(new Packet(bb, returnAddress));
|
76
|
+
outboundS += bb.remaining();
|
74
77
|
channel.register(selector, SelectionKey.OP_WRITE | SelectionKey.OP_READ, this);
|
75
78
|
}
|
76
79
|
} catch (ClosedChannelException e) {
|
@@ -82,6 +85,7 @@ public class EventableDatagramChannel implements EventableChannel {
|
|
82
85
|
try {
|
83
86
|
if ((!bCloseScheduled) && (bb.remaining() > 0)) {
|
84
87
|
outboundQ.addLast(new Packet (bb, new InetSocketAddress (recipAddress, recipPort)));
|
88
|
+
outboundS += bb.remaining();
|
85
89
|
channel.register(selector, SelectionKey.OP_WRITE | SelectionKey.OP_READ, this);
|
86
90
|
}
|
87
91
|
} catch (ClosedChannelException e) {
|
@@ -136,6 +140,7 @@ public class EventableDatagramChannel implements EventableChannel {
|
|
136
140
|
try {
|
137
141
|
// With a datagram socket, it's ok to send an empty buffer.
|
138
142
|
written = channel.send(p.bb, p.recipient);
|
143
|
+
outboundS -= written;
|
139
144
|
}
|
140
145
|
catch (IOException e) {
|
141
146
|
return false;
|
@@ -192,4 +197,5 @@ public class EventableDatagramChannel implements EventableChannel {
|
|
192
197
|
public boolean isWatchOnly() { return false; }
|
193
198
|
public boolean isNotifyReadable() { return false; }
|
194
199
|
public boolean isNotifyWritable() { return false; }
|
200
|
+
public long getOutboundDataSize() { return outboundS; }
|
195
201
|
}
|
@@ -54,6 +54,7 @@ public class EventableSocketChannel implements EventableChannel {
|
|
54
54
|
|
55
55
|
long binding;
|
56
56
|
LinkedList<ByteBuffer> outboundQ;
|
57
|
+
long outboundS;
|
57
58
|
|
58
59
|
boolean bCloseScheduled;
|
59
60
|
boolean bConnectPending;
|
@@ -61,6 +62,7 @@ public class EventableSocketChannel implements EventableChannel {
|
|
61
62
|
boolean bAttached;
|
62
63
|
boolean bNotifyReadable;
|
63
64
|
boolean bNotifyWritable;
|
65
|
+
boolean bPaused;
|
64
66
|
|
65
67
|
SSLEngine sslEngine;
|
66
68
|
SSLContext sslContext;
|
@@ -76,6 +78,7 @@ public class EventableSocketChannel implements EventableChannel {
|
|
76
78
|
bNotifyReadable = false;
|
77
79
|
bNotifyWritable = false;
|
78
80
|
outboundQ = new LinkedList<ByteBuffer>();
|
81
|
+
outboundS = 0;
|
79
82
|
}
|
80
83
|
|
81
84
|
public long getBinding() {
|
@@ -164,12 +167,14 @@ public class EventableSocketChannel implements EventableChannel {
|
|
164
167
|
sslEngine.wrap(bb, b);
|
165
168
|
b.flip();
|
166
169
|
outboundQ.addLast(b);
|
170
|
+
outboundS += b.remaining();
|
167
171
|
} catch (SSLException e) {
|
168
172
|
throw new RuntimeException ("ssl error");
|
169
173
|
}
|
170
174
|
}
|
171
175
|
else {
|
172
176
|
outboundQ.addLast(bb);
|
177
|
+
outboundS += bb.remaining();
|
173
178
|
}
|
174
179
|
|
175
180
|
updateEvents();
|
@@ -188,6 +193,8 @@ public class EventableSocketChannel implements EventableChannel {
|
|
188
193
|
throw new IOException ("eof");
|
189
194
|
}
|
190
195
|
|
196
|
+
public long getOutboundDataSize() { return outboundS; }
|
197
|
+
|
191
198
|
/**
|
192
199
|
* Called by the reactor when we have selected writable.
|
193
200
|
* Return false to indicate an error that should cause the connection to close.
|
@@ -196,23 +203,35 @@ public class EventableSocketChannel implements EventableChannel {
|
|
196
203
|
* this code is written, we're depending on a nonblocking write NOT TO CONSUME
|
197
204
|
* the whole outbound buffer in this case, rather than firing an exception.
|
198
205
|
* We should somehow verify that this is indeed Java's defined behavior.
|
199
|
-
* Also TODO, see if we can use gather I/O rather than one write at a time.
|
200
|
-
* Ought to be a big performance enhancer.
|
201
206
|
* @return
|
202
207
|
*/
|
203
208
|
public boolean writeOutboundData() throws IOException {
|
209
|
+
ByteBuffer[] bufs = new ByteBuffer[64];
|
210
|
+
int i;
|
211
|
+
long written, toWrite;
|
204
212
|
while (!outboundQ.isEmpty()) {
|
205
|
-
|
206
|
-
|
207
|
-
|
213
|
+
i = 0;
|
214
|
+
toWrite = 0;
|
215
|
+
written = 0;
|
216
|
+
while (i < 64 && !outboundQ.isEmpty()) {
|
217
|
+
bufs[i] = outboundQ.removeFirst();
|
218
|
+
toWrite += bufs[i].remaining();
|
219
|
+
i++;
|
220
|
+
}
|
221
|
+
if (toWrite > 0)
|
222
|
+
written = channel.write(bufs, 0, i);
|
208
223
|
|
224
|
+
outboundS -= written;
|
209
225
|
// Did we consume the whole outbound buffer? If yes,
|
210
226
|
// pop it off and keep looping. If no, the outbound network
|
211
227
|
// buffers are full, so break out of here.
|
212
|
-
if (
|
213
|
-
|
214
|
-
|
228
|
+
if (written < toWrite) {
|
229
|
+
while (i > 0 && bufs[i-1].remaining() > 0) {
|
230
|
+
outboundQ.addFirst(bufs[i-1]);
|
231
|
+
i--;
|
232
|
+
}
|
215
233
|
break;
|
234
|
+
}
|
216
235
|
}
|
217
236
|
|
218
237
|
if (outboundQ.isEmpty() && !bCloseScheduled) {
|
@@ -244,8 +263,10 @@ public class EventableSocketChannel implements EventableChannel {
|
|
244
263
|
|
245
264
|
public boolean scheduleClose (boolean afterWriting) {
|
246
265
|
// TODO: What the hell happens here if bConnectPending is set?
|
247
|
-
if (!afterWriting)
|
266
|
+
if (!afterWriting) {
|
248
267
|
outboundQ.clear();
|
268
|
+
outboundS = 0;
|
269
|
+
}
|
249
270
|
|
250
271
|
if (outboundQ.isEmpty())
|
251
272
|
return true;
|
@@ -331,6 +352,30 @@ public class EventableSocketChannel implements EventableChannel {
|
|
331
352
|
}
|
332
353
|
public boolean isNotifyWritable() { return bNotifyWritable; }
|
333
354
|
|
355
|
+
public boolean pause() {
|
356
|
+
if (bWatchOnly) {
|
357
|
+
throw new RuntimeException ("cannot pause/resume 'watch only' connections, set notify readable/writable instead");
|
358
|
+
}
|
359
|
+
boolean old = bPaused;
|
360
|
+
bPaused = true;
|
361
|
+
updateEvents();
|
362
|
+
return !old;
|
363
|
+
}
|
364
|
+
|
365
|
+
public boolean resume() {
|
366
|
+
if (bWatchOnly) {
|
367
|
+
throw new RuntimeException ("cannot pause/resume 'watch only' connections, set notify readable/writable instead");
|
368
|
+
}
|
369
|
+
boolean old = bPaused;
|
370
|
+
bPaused = false;
|
371
|
+
updateEvents();
|
372
|
+
return old;
|
373
|
+
}
|
374
|
+
|
375
|
+
public boolean isPaused() {
|
376
|
+
return bPaused;
|
377
|
+
}
|
378
|
+
|
334
379
|
private void updateEvents() {
|
335
380
|
if (channelKey == null)
|
336
381
|
return;
|
@@ -353,7 +398,7 @@ public class EventableSocketChannel implements EventableChannel {
|
|
353
398
|
if (bNotifyWritable)
|
354
399
|
events |= SelectionKey.OP_WRITE;
|
355
400
|
}
|
356
|
-
else
|
401
|
+
else if (!bPaused)
|
357
402
|
{
|
358
403
|
if (bConnectPending)
|
359
404
|
events |= SelectionKey.OP_CONNECT;
|
data/lib/em/buftok.rb
CHANGED
@@ -1,110 +1,59 @@
|
|
1
1
|
# BufferedTokenizer takes a delimiter upon instantiation, or acts line-based
|
2
2
|
# by default. It allows input to be spoon-fed from some outside source which
|
3
3
|
# receives arbitrary length datagrams which may-or-may-not contain the token
|
4
|
-
# by which entities are delimited.
|
5
|
-
#
|
6
|
-
# By default, new BufferedTokenizers will operate on lines delimited by "\n" by default
|
7
|
-
# or allow you to specify any delimiter token you so choose, which will then
|
8
|
-
# be used by String#split to tokenize the input data
|
9
|
-
#
|
10
|
-
# @example Using BufferedTokernizer to parse lines out of incoming data
|
11
|
-
#
|
12
|
-
# module LineBufferedConnection
|
13
|
-
# def receive_data(data)
|
14
|
-
# (@buffer ||= BufferedTokenizer.new).extract(data).each do |line|
|
15
|
-
# receive_line(line)
|
16
|
-
# end
|
17
|
-
# end
|
18
|
-
# end
|
19
|
-
#
|
20
|
-
# @author Tony Arcieri
|
21
|
-
# @author Martin Emde
|
4
|
+
# by which entities are delimited. In this respect it's ideally paired with
|
5
|
+
# something like EventMachine (http://rubyeventmachine.com/).
|
22
6
|
class BufferedTokenizer
|
23
|
-
#
|
24
|
-
#
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
# number of objects required for the operation.
|
7
|
+
# New BufferedTokenizers will operate on lines delimited by a delimiter,
|
8
|
+
# which is by default the global input delimiter $/ ("\n").
|
9
|
+
#
|
10
|
+
# The input buffer is stored as an array. This is by far the most efficient
|
11
|
+
# approach given language constraints (in C a linked list would be a more
|
12
|
+
# appropriate data structure). Segments of input data are stored in a list
|
13
|
+
# which is only joined when a token is reached, substantially reducing the
|
14
|
+
# number of objects required for the operation.
|
15
|
+
def initialize(delimiter = $/)
|
16
|
+
@delimiter = delimiter
|
34
17
|
@input = []
|
35
|
-
|
36
|
-
|
37
|
-
@input_size = 0
|
18
|
+
@tail = ''
|
19
|
+
@trim = @delimiter.length - 1
|
38
20
|
end
|
39
21
|
|
40
22
|
# Extract takes an arbitrary string of input data and returns an array of
|
41
|
-
# tokenized entities, provided there were any available to extract.
|
42
|
-
#
|
43
|
-
# @example
|
23
|
+
# tokenized entities, provided there were any available to extract. This
|
24
|
+
# makes for easy processing of datagrams using a pattern like:
|
44
25
|
#
|
45
|
-
# tokenizer.extract(data).
|
46
|
-
# map { |entity| Decode(entity) }.each { ... }
|
26
|
+
# tokenizer.extract(data).map { |entity| Decode(entity) }.each do ...
|
47
27
|
#
|
48
|
-
#
|
28
|
+
# Using -1 makes split to return "" if the token is at the end of
|
29
|
+
# the string, meaning the last element is the start of the next chunk.
|
49
30
|
def extract(data)
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
# input buffer or not (i.e. a literal edge case) Specifying -1 forces split to
|
54
|
-
# return "" in this case, meaning that the last entry in the list represents a
|
55
|
-
# new segment of data where the token has not been encountered
|
56
|
-
entities = data.split @delimiter, -1
|
57
|
-
|
58
|
-
# Check to see if the buffer has exceeded capacity, if we're imposing a limit
|
59
|
-
if @size_limit
|
60
|
-
raise 'input buffer full' if @input_size + entities.first.size > @size_limit
|
61
|
-
@input_size += entities.first.size
|
31
|
+
if @trim > 0
|
32
|
+
tail_end = @tail.slice!(-@trim, @trim) # returns nil if string is too short
|
33
|
+
data = tail_end + data if tail_end
|
62
34
|
end
|
63
35
|
|
64
|
-
|
65
|
-
|
66
|
-
@
|
67
|
-
|
68
|
-
# If the resulting array from the split is empty, the token was not encountered
|
69
|
-
# (not even at the end of the buffer). Since we've encountered no token-delimited
|
70
|
-
# entities this go-around, return an empty array.
|
71
|
-
return [] if entities.empty?
|
72
|
-
|
73
|
-
# At this point, we've hit a token, or potentially multiple tokens. Now we can bring
|
74
|
-
# together all the data we've buffered from earlier calls without hitting a token,
|
75
|
-
# and add it to our list of discovered entities.
|
76
|
-
entities.unshift @input.join
|
36
|
+
@input << @tail
|
37
|
+
entities = data.split(@delimiter, -1)
|
38
|
+
@tail = entities.shift
|
77
39
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
# passed to split. It represents the beginning of a new list of as-yet-untokenized
|
85
|
-
# data, so we add it to the start of the list.
|
86
|
-
@input << entities.pop
|
87
|
-
|
88
|
-
# Set the new input buffer size, provided we're keeping track
|
89
|
-
@input_size = @input.first.size if @size_limit
|
40
|
+
unless entities.empty?
|
41
|
+
@input << @tail
|
42
|
+
entities.unshift @input.join
|
43
|
+
@input.clear
|
44
|
+
@tail = entities.pop
|
45
|
+
end
|
90
46
|
|
91
|
-
# Now we're left with the list of extracted token-delimited entities we wanted
|
92
|
-
# in the first place. Hooray!
|
93
47
|
entities
|
94
48
|
end
|
95
49
|
|
96
50
|
# Flush the contents of the input buffer, i.e. return the input buffer even though
|
97
|
-
# a token has not yet been encountered
|
98
|
-
#
|
99
|
-
# @return [String]
|
51
|
+
# a token has not yet been encountered
|
100
52
|
def flush
|
53
|
+
@input << @tail
|
101
54
|
buffer = @input.join
|
102
55
|
@input.clear
|
56
|
+
@tail = "" # @tail.clear is slightly faster, but not supported on 1.8.7
|
103
57
|
buffer
|
104
58
|
end
|
105
|
-
|
106
|
-
# @return [Boolean]
|
107
|
-
def empty?
|
108
|
-
@input.empty?
|
109
|
-
end
|
110
59
|
end
|
@@ -23,8 +23,6 @@
|
|
23
23
|
#
|
24
24
|
#
|
25
25
|
|
26
|
-
|
27
|
-
|
28
26
|
module EventMachine
|
29
27
|
module Protocols
|
30
28
|
|
@@ -52,7 +50,6 @@ module EventMachine
|
|
52
50
|
# DNS: Some way to cache DNS lookups for hostnames we connect to. Ruby's
|
53
51
|
# DNS lookups are unbelievably slow.
|
54
52
|
# HEAD requests.
|
55
|
-
# Chunked transfer encoding.
|
56
53
|
# Convenience methods for requests. get, post, url, etc.
|
57
54
|
# SSL.
|
58
55
|
# Handle status codes like 304, 100, etc.
|
@@ -191,7 +188,7 @@ module EventMachine
|
|
191
188
|
if ary.length == 2
|
192
189
|
data = ary.last
|
193
190
|
if ary.first == ""
|
194
|
-
if (@content_length and @content_length > 0) || @connection_close
|
191
|
+
if (@content_length and @content_length > 0) || @chunked || @connection_close
|
195
192
|
@read_state = :content
|
196
193
|
else
|
197
194
|
dispatch_response
|
@@ -211,6 +208,8 @@ module EventMachine
|
|
211
208
|
@content_length ||= $'.to_i
|
212
209
|
elsif ary.first =~ /\Aconnection:\s*close/i
|
213
210
|
@connection_close = true
|
211
|
+
elsif ary.first =~ /\Atransfer-encoding:\s*chunked/i
|
212
|
+
@chunked = true
|
214
213
|
end
|
215
214
|
end
|
216
215
|
else
|
@@ -218,12 +217,32 @@ module EventMachine
|
|
218
217
|
data = ""
|
219
218
|
end
|
220
219
|
when :content
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
220
|
+
if @chunked && @chunk_length
|
221
|
+
bytes_needed = @chunk_length - @chunk_read
|
222
|
+
new_data = data[0, bytes_needed]
|
223
|
+
@chunk_read += new_data.length
|
224
|
+
@content += new_data
|
225
|
+
data = data[bytes_needed..-1] || ""
|
226
|
+
if @chunk_length == @chunk_read && data[0,2] == "\r\n"
|
227
|
+
@chunk_length = nil
|
228
|
+
data = data[2..-1]
|
229
|
+
end
|
230
|
+
elsif @chunked
|
231
|
+
if (m = data.match(/\A(\S*)\r\n/m))
|
232
|
+
data = data[m[0].length..-1]
|
233
|
+
@chunk_length = m[1].to_i(16)
|
234
|
+
@chunk_read = 0
|
235
|
+
if @chunk_length == 0
|
236
|
+
dispatch_response
|
237
|
+
@read_state = :base
|
238
|
+
end
|
239
|
+
end
|
240
|
+
elsif @content_length
|
241
|
+
# If there was no content-length header, we have to wait until the connection
|
242
|
+
# closes. Everything we get until that point is content.
|
243
|
+
# TODO: Must impose a content-size limit, and also must implement chunking.
|
244
|
+
# Also, must support either temporary files for large content, or calling
|
245
|
+
# a content-consumer block supplied by the user.
|
227
246
|
bytes_needed = @content_length - @content.length
|
228
247
|
@content += data[0, bytes_needed]
|
229
248
|
data = data[bytes_needed..-1] || ""
|
@@ -274,6 +293,5 @@ module EventMachine
|
|
274
293
|
end
|
275
294
|
end
|
276
295
|
end
|
277
|
-
|
278
296
|
end
|
279
297
|
end
|
@@ -32,7 +32,6 @@ module EventMachine
|
|
32
32
|
# for a version which is optimized for correctness with regard to binary text blocks
|
33
33
|
# that can switch back to line mode.
|
34
34
|
class LineAndTextProtocol < Connection
|
35
|
-
MaxLineLength = 16*1024
|
36
35
|
MaxBinaryLength = 32*1024*1024
|
37
36
|
|
38
37
|
def initialize *args
|
@@ -42,7 +41,7 @@ module EventMachine
|
|
42
41
|
def receive_data data
|
43
42
|
if @lbp_mode == :lines
|
44
43
|
begin
|
45
|
-
@lpb_buffer.extract(data).each do |line|
|
44
|
+
@lpb_buffer.extract(data).each do |line|
|
46
45
|
receive_line(line.chomp) if respond_to?(:receive_line)
|
47
46
|
end
|
48
47
|
rescue Exception
|
@@ -116,7 +115,7 @@ module EventMachine
|
|
116
115
|
#--
|
117
116
|
# For internal use, establish protocol baseline for handling lines.
|
118
117
|
def lbp_init_line_state
|
119
|
-
@lpb_buffer = BufferedTokenizer.new("\n"
|
118
|
+
@lpb_buffer = BufferedTokenizer.new("\n")
|
120
119
|
@lbp_mode = :lines
|
121
120
|
end
|
122
121
|
private :lbp_init_line_state
|
@@ -227,18 +227,26 @@ module EventMachine
|
|
227
227
|
process_unknown
|
228
228
|
end
|
229
229
|
end
|
230
|
-
|
230
|
+
|
231
231
|
# TODO - implement this properly, the implementation is a stub!
|
232
|
-
def
|
232
|
+
def process_help
|
233
233
|
send_data "250 Ok, but unimplemented\r\n"
|
234
234
|
end
|
235
|
+
|
236
|
+
# RFC2821, 3.5.3 Meaning of VRFY or EXPN Success Response:
|
237
|
+
# A server MUST NOT return a 250 code in response to a VRFY or EXPN
|
238
|
+
# command unless it has actually verified the address. In particular,
|
239
|
+
# a server MUST NOT return 250 if all it has done is to verify that the
|
240
|
+
# syntax given is valid. In that case, 502 (Command not implemented)
|
241
|
+
# or 500 (Syntax error, command unrecognized) SHOULD be returned.
|
242
|
+
#
|
235
243
|
# TODO - implement this properly, the implementation is a stub!
|
236
|
-
def
|
237
|
-
send_data "
|
244
|
+
def process_vrfy
|
245
|
+
send_data "502 Command not implemented\r\n"
|
238
246
|
end
|
239
247
|
# TODO - implement this properly, the implementation is a stub!
|
240
248
|
def process_expn
|
241
|
-
send_data "
|
249
|
+
send_data "502 Command not implemented\r\n"
|
242
250
|
end
|
243
251
|
|
244
252
|
#--
|
@@ -358,12 +366,23 @@ module EventMachine
|
|
358
366
|
def process_auth_line(line)
|
359
367
|
plain = line.unpack("m").first
|
360
368
|
_,user,psw = plain.split("\000")
|
361
|
-
|
369
|
+
|
370
|
+
succeeded = proc {
|
362
371
|
send_data "235 authentication ok\r\n"
|
363
372
|
@state << :auth
|
364
|
-
|
373
|
+
}
|
374
|
+
failed = proc {
|
365
375
|
send_data "535 invalid authentication\r\n"
|
376
|
+
}
|
377
|
+
auth = receive_plain_auth user,psw
|
378
|
+
|
379
|
+
if auth.respond_to?(:callback)
|
380
|
+
auth.callback(&succeeded)
|
381
|
+
auth.errback(&failed)
|
382
|
+
else
|
383
|
+
(auth ? succeeded : failed).call
|
366
384
|
end
|
385
|
+
|
367
386
|
@state.delete :auth_incomplete
|
368
387
|
end
|
369
388
|
|
data/lib/em/pure_ruby.rb
CHANGED
@@ -393,7 +393,7 @@ module EventMachine
|
|
393
393
|
100.times {
|
394
394
|
@loopbreak_port = rand(10000) + 40000
|
395
395
|
begin
|
396
|
-
@loopbreak_reader.bind "
|
396
|
+
@loopbreak_reader.bind "127.0.0.1", @loopbreak_port
|
397
397
|
bound = true
|
398
398
|
break
|
399
399
|
rescue
|
@@ -410,7 +410,7 @@ module EventMachine
|
|
410
410
|
|
411
411
|
def signal_loopbreak
|
412
412
|
#@loopbreak_writer.write '+' if @loopbreak_writer
|
413
|
-
@loopbreak_writer.send('+',0,"
|
413
|
+
@loopbreak_writer.send('+',0,"127.0.0.1",@loopbreak_port) if @loopbreak_writer
|
414
414
|
end
|
415
415
|
|
416
416
|
def set_timer_quantum interval_in_seconds
|
data/lib/em/version.rb
CHANGED
data/lib/jeventmachine.rb
CHANGED
@@ -268,6 +268,20 @@ module EventMachine
|
|
268
268
|
@em.getConnectionCount
|
269
269
|
end
|
270
270
|
|
271
|
+
def self.pause_connection(sig)
|
272
|
+
@em.pauseConnection(sig)
|
273
|
+
end
|
274
|
+
def self.resume_connection(sig)
|
275
|
+
@em.resumeConnection(sig)
|
276
|
+
end
|
277
|
+
def self.connection_paused?(sig)
|
278
|
+
@em.isConnectionPaused(sig)
|
279
|
+
end
|
280
|
+
def self._get_outbound_data_size(sig)
|
281
|
+
@em.getOutboundDataSize(sig)
|
282
|
+
end
|
283
|
+
|
284
|
+
|
271
285
|
def self.set_tls_parms(sig, params)
|
272
286
|
end
|
273
287
|
def self.start_tls(sig)
|
@@ -279,6 +293,9 @@ module EventMachine
|
|
279
293
|
def associate_callback_target sig
|
280
294
|
# No-op for the time being
|
281
295
|
end
|
296
|
+
def get_outbound_data_size
|
297
|
+
EM._get_outbound_data_size @signature
|
298
|
+
end
|
282
299
|
end
|
283
300
|
end
|
284
301
|
|
data/tests/em_test_helper.rb
CHANGED
data/tests/test_basic.rb
CHANGED
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'em_test_helper'
|
2
|
+
|
3
|
+
class TestConnectionWrite < Test::Unit::TestCase
|
4
|
+
|
5
|
+
# This test takes advantage of the fact that EM::_RunSelectOnce iterates over the connections twice:
|
6
|
+
# - once to determine which ones to call Write() on
|
7
|
+
# - and once to call Write() on each of them.
|
8
|
+
#
|
9
|
+
# But state may change in the meantime before Write() is finally called.
|
10
|
+
# And that is what we try to exploit to get Write() to be called when bWatchOnly is true, and bNotifyWritable is false,
|
11
|
+
# to cause an assertion failure.
|
12
|
+
|
13
|
+
module SimpleClient
|
14
|
+
def notify_writable
|
15
|
+
$conn2.notify_writable = false # Being naughty in callback
|
16
|
+
# If this doesn't crash anything, the test passed!
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_with_naughty_callback
|
21
|
+
EM.run do
|
22
|
+
r1, w1 = IO.pipe
|
23
|
+
r2, w2 = IO.pipe
|
24
|
+
|
25
|
+
# Adding EM.watches
|
26
|
+
$conn1 = EM.watch(r1, SimpleClient)
|
27
|
+
$conn2 = EM.watch(r2, SimpleClient)
|
28
|
+
|
29
|
+
$conn1.notify_writable = true
|
30
|
+
$conn2.notify_writable = true
|
31
|
+
|
32
|
+
EM.stop
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/tests/test_httpclient.rb
CHANGED
@@ -187,4 +187,47 @@ class TestHttpClient < Test::Unit::TestCase
|
|
187
187
|
assert ok
|
188
188
|
end
|
189
189
|
|
190
|
+
#-----------------------------------------
|
191
|
+
|
192
|
+
# Test a server that returns chunked encoding
|
193
|
+
#
|
194
|
+
class ChunkedEncodingContent < EventMachine::Connection
|
195
|
+
def initialize *args
|
196
|
+
super
|
197
|
+
end
|
198
|
+
def receive_data data
|
199
|
+
send_data ["HTTP/1.1 200 OK",
|
200
|
+
"Server: nginx/0.7.67",
|
201
|
+
"Date: Sat, 23 Oct 2010 16:41:32 GMT",
|
202
|
+
"Content-Type: application/json",
|
203
|
+
"Transfer-Encoding: chunked",
|
204
|
+
"Connection: keep-alive",
|
205
|
+
"",
|
206
|
+
"1800",
|
207
|
+
"chunk1" * 1024,
|
208
|
+
"5a",
|
209
|
+
"chunk2" * 15,
|
210
|
+
"0",
|
211
|
+
""].join("\r\n")
|
212
|
+
close_connection_after_writing
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
def test_http_chunked_encoding_content
|
217
|
+
ok = false
|
218
|
+
EventMachine.run {
|
219
|
+
EventMachine.start_server "127.0.0.1", 9701, ChunkedEncodingContent
|
220
|
+
c = EventMachine::Protocols::HttpClient.send :request, :host => "127.0.0.1", :port => 9701
|
221
|
+
c.callback {|result|
|
222
|
+
if result[:content] == "chunk1" * 1024 + "chunk2" * 15
|
223
|
+
ok = true
|
224
|
+
end
|
225
|
+
EventMachine.stop
|
226
|
+
}
|
227
|
+
}
|
228
|
+
assert ok
|
229
|
+
end
|
230
|
+
|
190
231
|
end
|
232
|
+
|
233
|
+
|
data/tests/test_pause.rb
CHANGED
@@ -82,14 +82,19 @@ class TestPause < Test::Unit::TestCase
|
|
82
82
|
end
|
83
83
|
end
|
84
84
|
|
85
|
+
buf = 'a' * 1024
|
86
|
+
|
85
87
|
EM.run do
|
86
88
|
EM.start_server "127.0.0.1", @port, test_server
|
87
89
|
cli = EM.connect "127.0.0.1", @port
|
88
|
-
|
90
|
+
128.times do
|
91
|
+
cli.send_data buf
|
92
|
+
end
|
89
93
|
end
|
90
94
|
|
91
95
|
assert_equal 1, incoming.size
|
92
|
-
|
96
|
+
assert incoming[0].bytesize > buf.bytesize
|
97
|
+
assert incoming[0].bytesize < buf.bytesize * 128
|
93
98
|
end
|
94
99
|
else
|
95
100
|
warn "EM.pause_connection not implemented, skipping tests in #{__FILE__}"
|
data/tests/test_process_watch.rb
CHANGED
data/tests/test_ssl_methods.rb
CHANGED
data/tests/test_ssl_verify.rb
CHANGED
@@ -54,6 +54,7 @@ class TestSslVerify < Test::Unit::TestCase
|
|
54
54
|
|
55
55
|
def test_accept_server
|
56
56
|
omit_unless(EM.ssl?)
|
57
|
+
omit_if(rbx?)
|
57
58
|
$client_handshake_completed, $server_handshake_completed = false, false
|
58
59
|
EM.run {
|
59
60
|
EM.start_server("127.0.0.1", 16784, AcceptServer)
|
@@ -67,6 +68,7 @@ class TestSslVerify < Test::Unit::TestCase
|
|
67
68
|
|
68
69
|
def test_deny_server
|
69
70
|
omit_unless(EM.ssl?)
|
71
|
+
omit_if(rbx?)
|
70
72
|
$client_handshake_completed, $server_handshake_completed = false, false
|
71
73
|
EM.run {
|
72
74
|
EM.start_server("127.0.0.1", 16784, DenyServer)
|
metadata
CHANGED
@@ -1,101 +1,69 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: eventmachine
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
5
|
-
prerelease:
|
4
|
+
version: 1.0.6
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Francis Cianfrocca
|
9
8
|
- Aman Gupta
|
10
|
-
autorequire:
|
9
|
+
autorequire:
|
11
10
|
bindir: bin
|
12
11
|
cert_chain: []
|
13
|
-
date: 2015-02-
|
12
|
+
date: 2015-02-04 00:00:00.000000000 Z
|
14
13
|
dependencies:
|
15
14
|
- !ruby/object:Gem::Dependency
|
16
15
|
name: test-unit
|
17
|
-
|
18
|
-
none: false
|
16
|
+
version_requirements: !ruby/object:Gem::Requirement
|
19
17
|
requirements:
|
20
18
|
- - ~>
|
21
19
|
- !ruby/object:Gem::Version
|
22
20
|
version: '2.0'
|
23
|
-
|
24
|
-
prerelease: false
|
25
|
-
version_requirements: !ruby/object:Gem::Requirement
|
26
|
-
none: false
|
21
|
+
requirement: !ruby/object:Gem::Requirement
|
27
22
|
requirements:
|
28
23
|
- - ~>
|
29
24
|
- !ruby/object:Gem::Version
|
30
25
|
version: '2.0'
|
26
|
+
prerelease: false
|
27
|
+
type: :development
|
31
28
|
- !ruby/object:Gem::Dependency
|
32
29
|
name: rake-compiler
|
33
|
-
|
34
|
-
none: false
|
30
|
+
version_requirements: !ruby/object:Gem::Requirement
|
35
31
|
requirements:
|
36
32
|
- - ~>
|
37
33
|
- !ruby/object:Gem::Version
|
38
34
|
version: 0.8.3
|
39
|
-
|
40
|
-
prerelease: false
|
41
|
-
version_requirements: !ruby/object:Gem::Requirement
|
42
|
-
none: false
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
43
36
|
requirements:
|
44
37
|
- - ~>
|
45
38
|
- !ruby/object:Gem::Version
|
46
39
|
version: 0.8.3
|
40
|
+
prerelease: false
|
41
|
+
type: :development
|
47
42
|
- !ruby/object:Gem::Dependency
|
48
43
|
name: yard
|
49
|
-
requirement: !ruby/object:Gem::Requirement
|
50
|
-
none: false
|
51
|
-
requirements:
|
52
|
-
- - ! '>='
|
53
|
-
- !ruby/object:Gem::Version
|
54
|
-
version: 0.8.5.2
|
55
|
-
type: :development
|
56
|
-
prerelease: false
|
57
44
|
version_requirements: !ruby/object:Gem::Requirement
|
58
|
-
none: false
|
59
45
|
requirements:
|
60
|
-
- -
|
46
|
+
- - '>='
|
61
47
|
- !ruby/object:Gem::Version
|
62
48
|
version: 0.8.5.2
|
63
|
-
- !ruby/object:Gem::Dependency
|
64
|
-
name: bluecloth
|
65
49
|
requirement: !ruby/object:Gem::Requirement
|
66
|
-
none: false
|
67
50
|
requirements:
|
68
|
-
- -
|
51
|
+
- - '>='
|
69
52
|
- !ruby/object:Gem::Version
|
70
|
-
version:
|
71
|
-
type: :development
|
53
|
+
version: 0.8.5.2
|
72
54
|
prerelease: false
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
- !ruby/object:Gem::Version
|
78
|
-
version: '0'
|
79
|
-
description: ! 'EventMachine implements a fast, single-threaded engine for arbitrary
|
80
|
-
network
|
81
|
-
|
82
|
-
communications. It''s extremely easy to use in Ruby. EventMachine wraps all
|
83
|
-
|
55
|
+
type: :development
|
56
|
+
description: |-
|
57
|
+
EventMachine implements a fast, single-threaded engine for arbitrary network
|
58
|
+
communications. It's extremely easy to use in Ruby. EventMachine wraps all
|
84
59
|
interactions with IP sockets, allowing programs to concentrate on the
|
85
|
-
|
86
60
|
implementation of network protocols. It can be used to create both network
|
87
|
-
|
88
61
|
servers and clients. To create a server or client, a Ruby program only needs
|
89
|
-
|
90
62
|
to specify the IP address and port, and provide a Module that implements the
|
91
|
-
|
92
63
|
communications protocol. Implementations of several standard network protocols
|
93
|
-
|
94
64
|
are provided with the package, primarily to serve as examples. The real goal
|
95
|
-
|
96
65
|
of EventMachine is to enable programs to easily interface with other programs
|
97
|
-
|
98
|
-
using TCP/IP, especially if custom protocols are required.'
|
66
|
+
using TCP/IP, especially if custom protocols are required.
|
99
67
|
email:
|
100
68
|
- garbagecat10@gmail.com
|
101
69
|
- aman@tmm1.net
|
@@ -237,6 +205,7 @@ files:
|
|
237
205
|
- tests/test_channel.rb
|
238
206
|
- tests/test_completion.rb
|
239
207
|
- tests/test_connection_count.rb
|
208
|
+
- tests/test_connection_write.rb
|
240
209
|
- tests/test_defer.rb
|
241
210
|
- tests/test_deferrable.rb
|
242
211
|
- tests/test_epoll.rb
|
@@ -291,7 +260,8 @@ homepage: http://rubyeventmachine.com
|
|
291
260
|
licenses:
|
292
261
|
- Ruby
|
293
262
|
- GPL
|
294
|
-
|
263
|
+
metadata: {}
|
264
|
+
post_install_message:
|
295
265
|
rdoc_options:
|
296
266
|
- --title
|
297
267
|
- EventMachine
|
@@ -304,21 +274,19 @@ rdoc_options:
|
|
304
274
|
require_paths:
|
305
275
|
- lib
|
306
276
|
required_ruby_version: !ruby/object:Gem::Requirement
|
307
|
-
none: false
|
308
277
|
requirements:
|
309
|
-
- -
|
278
|
+
- - '>='
|
310
279
|
- !ruby/object:Gem::Version
|
311
280
|
version: '0'
|
312
281
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
313
|
-
none: false
|
314
282
|
requirements:
|
315
|
-
- -
|
283
|
+
- - '>='
|
316
284
|
- !ruby/object:Gem::Version
|
317
285
|
version: '0'
|
318
286
|
requirements: []
|
319
287
|
rubyforge_project: eventmachine
|
320
|
-
rubygems_version: 1.
|
321
|
-
signing_key:
|
322
|
-
specification_version:
|
288
|
+
rubygems_version: 2.1.9
|
289
|
+
signing_key:
|
290
|
+
specification_version: 4
|
323
291
|
summary: Ruby/EventMachine library
|
324
292
|
test_files: []
|