jubilee 2.1.0.Alpha1-java → 2.1.0.beta-java
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 +4 -4
- data/.travis.yml +2 -2
- data/CHANGELOG +9 -0
- data/README.md +12 -7
- data/jars/vertx-core-2.1.1.jar +0 -0
- data/java/src/jubilee/JubileeService.java +3 -3
- data/java/src/org/jruby/jubilee/Const.java +1 -1
- data/java/src/org/jruby/jubilee/JubileeVerticle.java +29 -4
- data/java/src/org/jruby/jubilee/RackApplication.java +38 -35
- data/java/src/org/jruby/jubilee/RackEnvironment.java +57 -23
- data/java/src/org/jruby/jubilee/RackEnvironmentHash.java +64 -11
- data/java/src/org/jruby/jubilee/RackInput.java +13 -10
- data/java/src/org/jruby/jubilee/RubyCallable.java +52 -0
- data/java/src/org/jruby/jubilee/RubyChannel.java +89 -0
- data/java/src/org/jruby/jubilee/RubyHttpServerResponse.java +72 -60
- data/java/src/org/jruby/jubilee/RubyNetSocket.java +169 -0
- data/java/src/org/jruby/jubilee/RubyPlatformManager.java +129 -113
- data/java/src/org/jruby/jubilee/impl/RubyIORackInput.java +9 -9
- data/java/src/org/jruby/jubilee/impl/RubyNullIO.java +1 -1
- data/java/src/org/jruby/jubilee/utils/RubyHelper.java +0 -6
- data/java/src/org/jruby/jubilee/vertx/JubileeVertx.java +12 -11
- data/jubilee.gemspec +43 -20
- data/lib/jubilee.rb +0 -1
- data/lib/jubilee/cli.rb +5 -3
- data/lib/jubilee/configuration.rb +2 -7
- data/lib/jubilee/const.rb +30 -28
- data/lib/jubilee/response.rb +40 -5
- data/lib/jubilee/server.rb +0 -3
- data/lib/jubilee/version.rb +1 -1
- data/spec/apps/rails4/basic/Gemfile +0 -2
- data/spec/apps/rails4/basic/Gemfile.lock +0 -7
- data/spec/integration/basic_rack_spec.rb +4 -3
- data/spec/integration/basic_rails4_spec.rb +4 -3
- data/spec/integration/basic_sinatra_spec.rb +4 -4
- data/spec/spec_helper.rb +1 -0
- data/test/{config → apps}/app.rb +0 -0
- data/test/apps/checker.ru +5 -10
- data/test/apps/chunked.ru +3 -0
- data/test/{config → apps}/config.ru +0 -0
- data/test/apps/content_length.ru +3 -0
- data/test/apps/hex.ru +4 -0
- data/test/apps/hijack.ru +7 -0
- data/test/apps/hijack2.ru +7 -0
- data/test/apps/huge.ru +4 -0
- data/test/apps/method_override.ru +1 -1
- data/test/apps/overwrite_check.ru +3 -2
- data/test/apps/persistent.rb +14 -0
- data/test/apps/persistent.ru +3 -0
- data/test/apps/rack_input.ru +5 -0
- data/test/apps/self_chunked.ru +6 -0
- data/test/apps/sha1.ru +2 -0
- data/test/apps/simple.ru +10 -1
- data/test/jubilee/test_cli.rb +1 -1
- data/test/jubilee/test_configuration.rb +1 -3
- data/test/jubilee/test_hijack.rb +27 -0
- data/test/jubilee/test_persistent.rb +208 -0
- data/test/jubilee/test_rack_server.rb +29 -68
- data/test/jubilee/test_server.rb +49 -15
- data/test/jubilee/test_upload.rb +13 -60
- data/test/test_helper.rb +2 -2
- metadata +19 -9
- data/java/src/org/jruby/jubilee/RubyServer.java +0 -159
- data/lib/jubilee/jubilee.jar +0 -0
- data/lib/rack/chunked.rb +0 -38
- data/test/config/app.ru +0 -3
- data/test/jubilee/test_response.rb +0 -270
@@ -6,19 +6,19 @@ import org.jruby.runtime.builtin.IRubyObject;
|
|
6
6
|
|
7
7
|
/**
|
8
8
|
* @author isaiah
|
9
|
+
* @author nicksieger
|
9
10
|
* @since Nov 26, 2012
|
10
|
-
*
|
11
|
+
* <p/>
|
11
12
|
* Specification for Rack input, translated to a Java interface.
|
12
|
-
* @author nicksieger
|
13
13
|
*/
|
14
|
-
public interface RackInput
|
15
|
-
{
|
14
|
+
public interface RackInput {
|
16
15
|
/**
|
17
16
|
* gets must be called without arguments and return a string, or nil on EOF.
|
17
|
+
*
|
18
18
|
* @param context it's a JRuby thing
|
19
19
|
* @return a string, or nil on EOF
|
20
20
|
*/
|
21
|
-
IRubyObject gets(
|
21
|
+
IRubyObject gets(ThreadContext context);
|
22
22
|
|
23
23
|
/**
|
24
24
|
* read behaves like IO#read. Its signature is read([length, [buffer]]). If given,
|
@@ -29,29 +29,32 @@ public interface RackInput
|
|
29
29
|
* method returns nil if length is given and not nil, or "" if length is not
|
30
30
|
* given or is nil. If buffer is given, then the read data will be placed into
|
31
31
|
* buffer instead of a newly created String object.
|
32
|
+
*
|
32
33
|
* @param context it's a JRuby thing
|
33
|
-
* @param args
|
34
|
+
* @param args [length, [buffer]]
|
34
35
|
* @return nil if length is given and not nil, or "" if length is not given or nil
|
35
36
|
*/
|
36
|
-
IRubyObject read(
|
37
|
+
IRubyObject read(ThreadContext context, IRubyObject[] args);
|
37
38
|
|
38
39
|
/**
|
39
40
|
* each must be called without arguments and only yield Strings.
|
41
|
+
*
|
40
42
|
* @param context it's a JRuby thing
|
41
|
-
* @param block
|
43
|
+
* @param block that receives yield of Strings
|
42
44
|
* @return pretty much nil
|
43
45
|
*/
|
44
|
-
public IRubyObject each(
|
46
|
+
public IRubyObject each(ThreadContext context, Block block);
|
45
47
|
|
46
48
|
/**
|
47
49
|
* rewind must be called without arguments. It rewinds the input stream back
|
48
50
|
* to the beginning. It must not raise Errno::ESPIPE: that is, it may not be
|
49
51
|
* a pipe or a socket. Therefore, handler developers must buffer the input
|
50
52
|
* data into some rewindable object if the underlying input stream is not rewindable.
|
53
|
+
*
|
51
54
|
* @param context it's a JRuby thing
|
52
55
|
* @return pretty much nil
|
53
56
|
*/
|
54
|
-
public IRubyObject rewind(
|
57
|
+
public IRubyObject rewind(ThreadContext context);
|
55
58
|
|
56
59
|
/**
|
57
60
|
* Close the input. Exposed only to the Java side because the Rack spec says
|
@@ -0,0 +1,52 @@
|
|
1
|
+
package org.jruby.jubilee;
|
2
|
+
|
3
|
+
import org.jruby.Ruby;
|
4
|
+
import org.jruby.RubyClass;
|
5
|
+
import org.jruby.RubyModule;
|
6
|
+
import org.jruby.RubyObject;
|
7
|
+
import org.jruby.anno.JRubyMethod;
|
8
|
+
import org.jruby.runtime.ObjectAllocator;
|
9
|
+
import org.jruby.runtime.ThreadContext;
|
10
|
+
import org.jruby.runtime.builtin.IRubyObject;
|
11
|
+
|
12
|
+
/**
|
13
|
+
* A RubyClass that expose a call method, like a proc
|
14
|
+
* There must be a class in JRuby for this, but I just couldn't find it.
|
15
|
+
*/
|
16
|
+
public class RubyCallable extends RubyObject {
|
17
|
+
private Callable callable;
|
18
|
+
|
19
|
+
public static RubyClass createClallableClass(final Ruby runtime) {
|
20
|
+
RubyModule jubilee = runtime.getOrCreateModule("Jubilee");
|
21
|
+
RubyClass klazz = jubilee.defineClassUnder("Callable", runtime.getObject(), new ObjectAllocator() {
|
22
|
+
@Override
|
23
|
+
public IRubyObject allocate(Ruby ruby, RubyClass rubyClass) {
|
24
|
+
return new RubyCallable(ruby, rubyClass);
|
25
|
+
}
|
26
|
+
});
|
27
|
+
klazz.defineAnnotatedMethods(RubyCallable.class);
|
28
|
+
return klazz;
|
29
|
+
}
|
30
|
+
|
31
|
+
public RubyCallable(Ruby ruby, RubyClass rubyClass) {
|
32
|
+
super(ruby, rubyClass);
|
33
|
+
}
|
34
|
+
|
35
|
+
public RubyCallable(Ruby ruby, RubyClass rubyClass, Callable callable) {
|
36
|
+
super(ruby, rubyClass);
|
37
|
+
this.callable = callable;
|
38
|
+
}
|
39
|
+
|
40
|
+
@JRubyMethod
|
41
|
+
public IRubyObject call(ThreadContext context) {
|
42
|
+
this.callable.call();
|
43
|
+
return context.runtime.getNil();
|
44
|
+
}
|
45
|
+
|
46
|
+
public static abstract class Callable {
|
47
|
+
public void call() {
|
48
|
+
}
|
49
|
+
|
50
|
+
;
|
51
|
+
}
|
52
|
+
}
|
@@ -0,0 +1,89 @@
|
|
1
|
+
package org.jruby.jubilee;
|
2
|
+
|
3
|
+
import io.netty.channel.Channel;
|
4
|
+
import org.jruby.Ruby;
|
5
|
+
import org.jruby.RubyClass;
|
6
|
+
import org.jruby.RubyModule;
|
7
|
+
import org.jruby.RubyObject;
|
8
|
+
import org.jruby.anno.JRubyClass;
|
9
|
+
import org.jruby.anno.JRubyMethod;
|
10
|
+
import org.jruby.runtime.ObjectAllocator;
|
11
|
+
import org.jruby.runtime.ThreadContext;
|
12
|
+
import org.jruby.runtime.builtin.IRubyObject;
|
13
|
+
|
14
|
+
/**
|
15
|
+
* Created by isaiah on 25/12/2013.
|
16
|
+
*/
|
17
|
+
@JRubyClass(name = "Channel")
|
18
|
+
public class RubyChannel extends RubyObject {
|
19
|
+
private Channel chan;
|
20
|
+
|
21
|
+
public static RubyClass createChannelClass(final Ruby runtime) {
|
22
|
+
RubyModule vertxModule = runtime.getOrCreateModule("Jubilee");
|
23
|
+
RubyClass klazz = vertxModule.defineClassUnder("Channel", runtime.getObject(), new ObjectAllocator() {
|
24
|
+
@Override
|
25
|
+
public IRubyObject allocate(Ruby ruby, RubyClass rubyClass) {
|
26
|
+
return new RubyChannel(ruby, rubyClass);
|
27
|
+
}
|
28
|
+
});
|
29
|
+
klazz.defineAnnotatedMethods(RubyChannel.class);
|
30
|
+
return klazz;
|
31
|
+
}
|
32
|
+
|
33
|
+
public RubyChannel(Ruby ruby, RubyClass rubyClass) {
|
34
|
+
super(ruby, rubyClass);
|
35
|
+
}
|
36
|
+
|
37
|
+
@JRubyMethod
|
38
|
+
public IRubyObject read(ThreadContext context) throws InterruptedException {
|
39
|
+
return context.runtime.getNil();
|
40
|
+
}
|
41
|
+
|
42
|
+
@JRubyMethod
|
43
|
+
public IRubyObject write(ThreadContext context) {
|
44
|
+
return context.runtime.getNil();
|
45
|
+
|
46
|
+
}
|
47
|
+
|
48
|
+
@JRubyMethod
|
49
|
+
public IRubyObject readNonBlock(ThreadContext context) {
|
50
|
+
|
51
|
+
return context.runtime.getNil();
|
52
|
+
}
|
53
|
+
|
54
|
+
@JRubyMethod
|
55
|
+
public IRubyObject writeNonBlock(ThreadContext context) {
|
56
|
+
|
57
|
+
return context.runtime.getNil();
|
58
|
+
}
|
59
|
+
|
60
|
+
@JRubyMethod
|
61
|
+
public IRubyObject flush(ThreadContext context) {
|
62
|
+
|
63
|
+
return context.runtime.getNil();
|
64
|
+
}
|
65
|
+
|
66
|
+
@JRubyMethod
|
67
|
+
public IRubyObject close(ThreadContext context) {
|
68
|
+
|
69
|
+
return context.runtime.getNil();
|
70
|
+
}
|
71
|
+
|
72
|
+
@JRubyMethod
|
73
|
+
public IRubyObject closeRead(ThreadContext context) {
|
74
|
+
|
75
|
+
return context.runtime.getNil();
|
76
|
+
}
|
77
|
+
|
78
|
+
@JRubyMethod
|
79
|
+
public IRubyObject closeWrite(ThreadContext context) {
|
80
|
+
|
81
|
+
return context.runtime.getNil();
|
82
|
+
}
|
83
|
+
|
84
|
+
@JRubyMethod(name = "closed?")
|
85
|
+
public IRubyObject isClosed(ThreadContext context) {
|
86
|
+
|
87
|
+
return context.runtime.getNil();
|
88
|
+
}
|
89
|
+
}
|
@@ -6,6 +6,8 @@ import org.jruby.anno.JRubyMethod;
|
|
6
6
|
import org.jruby.runtime.ObjectAllocator;
|
7
7
|
import org.jruby.runtime.ThreadContext;
|
8
8
|
import org.jruby.runtime.builtin.IRubyObject;
|
9
|
+
import org.vertx.java.core.http.HttpServer;
|
10
|
+
import org.vertx.java.core.http.HttpServerRequest;
|
9
11
|
import org.vertx.java.core.http.HttpServerResponse;
|
10
12
|
|
11
13
|
import java.util.Arrays;
|
@@ -15,74 +17,84 @@ import java.util.Arrays;
|
|
15
17
|
*/
|
16
18
|
@JRubyClass(name = "HttpServerResponse")
|
17
19
|
public class RubyHttpServerResponse extends RubyObject {
|
18
|
-
|
19
|
-
|
20
|
+
private HttpServerResponse resp;
|
21
|
+
private HttpServerRequest req;
|
22
|
+
private String lineSeparator;
|
20
23
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
24
|
+
public static RubyClass createHttpServerResponseClass(final Ruby runtime) {
|
25
|
+
RubyModule mJubilee = runtime.getOrCreateModule("Jubilee");
|
26
|
+
RubyClass klazz = mJubilee.defineClassUnder("HttpServerResponse", runtime.getObject(), new ObjectAllocator() {
|
27
|
+
@Override
|
28
|
+
public IRubyObject allocate(Ruby ruby, RubyClass rubyClass) {
|
29
|
+
return new RubyHttpServerResponse(ruby, rubyClass);
|
30
|
+
}
|
31
|
+
});
|
32
|
+
klazz.defineAnnotatedMethods(RubyHttpServerResponse.class);
|
33
|
+
return klazz;
|
34
|
+
}
|
32
35
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
+
public RubyHttpServerResponse(Ruby ruby, RubyClass rubyClass) {
|
37
|
+
super(ruby, rubyClass);
|
38
|
+
}
|
36
39
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
40
|
+
public RubyHttpServerResponse(Ruby ruby, RubyClass rubyClass, HttpServerRequest request) {
|
41
|
+
super(ruby, rubyClass);
|
42
|
+
this.resp = request.response();
|
43
|
+
this.req = request;
|
44
|
+
this.lineSeparator = System.getProperty("line.separator");
|
45
|
+
}
|
42
46
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
47
|
+
@JRubyMethod
|
48
|
+
public IRubyObject write(ThreadContext context, IRubyObject string) {
|
49
|
+
this.resp.write(string.asJavaString());
|
50
|
+
return context.runtime.getNil();
|
51
|
+
}
|
48
52
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
53
|
+
@JRubyMethod(name = "status_code=")
|
54
|
+
public IRubyObject setStatusCode(ThreadContext context, IRubyObject statusCode) {
|
55
|
+
this.resp.setStatusCode(RubyNumeric.num2int(statusCode));
|
56
|
+
return context.runtime.getNil();
|
57
|
+
}
|
54
58
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
59
|
+
@JRubyMethod(name = "chunked=")
|
60
|
+
public IRubyObject setChunked(ThreadContext context, IRubyObject chunked) {
|
61
|
+
this.resp.setChunked(chunked.isTrue());
|
62
|
+
return context.runtime.getNil();
|
63
|
+
}
|
60
64
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
65
|
+
@JRubyMethod(name = "put_header")
|
66
|
+
public IRubyObject putHeader(ThreadContext context, IRubyObject key, IRubyObject val) {
|
67
|
+
String cookie = val.asJavaString();
|
68
|
+
if (cookie.indexOf(this.lineSeparator) != -1)
|
69
|
+
this.resp.putHeader(key.asJavaString(),
|
70
|
+
Arrays.asList(val.asJavaString().split(this.lineSeparator)));
|
71
|
+
else this.resp.putHeader(key.asJavaString(), val.asJavaString());
|
72
|
+
return context.runtime.getNil();
|
73
|
+
}
|
70
74
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
75
|
+
@JRubyMethod(name = "send_file")
|
76
|
+
public IRubyObject sendFile(ThreadContext context, IRubyObject filePath) {
|
77
|
+
this.resp.sendFile(filePath.asJavaString());
|
78
|
+
return context.runtime.getNil();
|
79
|
+
}
|
76
80
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
81
|
+
@JRubyMethod
|
82
|
+
public IRubyObject end(ThreadContext context) {
|
83
|
+
this.resp.end();
|
84
|
+
return context.runtime.getNil();
|
85
|
+
}
|
82
86
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
87
|
+
@JRubyMethod(name = "put_default_headers")
|
88
|
+
public IRubyObject putDefaultHeaders(ThreadContext context) {
|
89
|
+
this.resp.putHeader("Server", Const.JUBILEE_VERSION);
|
90
|
+
return context.runtime.getNil();
|
91
|
+
}
|
92
|
+
|
93
|
+
//TODO(isaiah) At the moment Vertx doesn't support hijack the response socket,
|
94
|
+
// between request.response() and request.netSocket() only one can be invoked.
|
95
|
+
@JRubyMethod
|
96
|
+
public IRubyObject net_socket(ThreadContext context) {
|
97
|
+
RubyClass netSocketClass = (RubyClass) context.runtime.getClassFromPath("Jubilee::NetSocket");
|
98
|
+
return new RubyNetSocket(context.runtime, netSocketClass, this.req.netSocket());
|
99
|
+
}
|
88
100
|
}
|
@@ -0,0 +1,169 @@
|
|
1
|
+
package org.jruby.jubilee;
|
2
|
+
|
3
|
+
import io.netty.buffer.ByteBuf;
|
4
|
+
import io.netty.buffer.Unpooled;
|
5
|
+
import org.jruby.*;
|
6
|
+
import org.jruby.anno.JRubyClass;
|
7
|
+
import org.jruby.anno.JRubyMethod;
|
8
|
+
import org.jruby.runtime.ObjectAllocator;
|
9
|
+
import org.jruby.runtime.ThreadContext;
|
10
|
+
import org.jruby.runtime.builtin.IRubyObject;
|
11
|
+
import org.jruby.util.ByteList;
|
12
|
+
import org.vertx.java.core.Handler;
|
13
|
+
import org.vertx.java.core.VoidHandler;
|
14
|
+
import org.vertx.java.core.buffer.Buffer;
|
15
|
+
import org.vertx.java.core.net.NetSocket;
|
16
|
+
import org.vertx.java.core.streams.WriteStream;
|
17
|
+
|
18
|
+
import java.util.concurrent.atomic.AtomicBoolean;
|
19
|
+
|
20
|
+
/**
|
21
|
+
* This class create a ruby IO interface by wrapping a Vertx NetSocket object.
|
22
|
+
* <p/>
|
23
|
+
* Not threadsafe.
|
24
|
+
*/
|
25
|
+
@JRubyClass(name="NetSocket")
|
26
|
+
public class RubyNetSocket extends RubyObject {
|
27
|
+
private NetSocket sock;
|
28
|
+
private ByteBuf buf;
|
29
|
+
private AtomicBoolean eof;
|
30
|
+
private boolean readClosed = false;
|
31
|
+
private boolean writeClosed = false;
|
32
|
+
private boolean closed = false;
|
33
|
+
|
34
|
+
private final static int BUFSIZE = 4096 * 2;
|
35
|
+
|
36
|
+
public static RubyClass createNetSocketClass(final Ruby runtime) {
|
37
|
+
RubyModule jubilee = runtime.getOrCreateModule("Jubilee");
|
38
|
+
RubyClass klazz = jubilee.defineClassUnder("NetSocket", runtime.getObject(), new ObjectAllocator() {
|
39
|
+
@Override
|
40
|
+
public IRubyObject allocate(Ruby ruby, RubyClass rubyClass) {
|
41
|
+
return new RubyNetSocket(ruby, rubyClass);
|
42
|
+
}
|
43
|
+
});
|
44
|
+
klazz.defineAnnotatedMethods(RubyNetSocket.class);
|
45
|
+
return klazz;
|
46
|
+
}
|
47
|
+
|
48
|
+
public RubyNetSocket(Ruby ruby, RubyClass rubyClass) {
|
49
|
+
super(ruby, rubyClass);
|
50
|
+
}
|
51
|
+
|
52
|
+
public RubyNetSocket(Ruby ruby, RubyClass rubyClass, NetSocket socket) {
|
53
|
+
super(ruby, rubyClass);
|
54
|
+
this.sock = socket;
|
55
|
+
this.buf = Unpooled.buffer(BUFSIZE);
|
56
|
+
this.eof = new AtomicBoolean(false);
|
57
|
+
this.sock.dataHandler(new Handler<Buffer>() {
|
58
|
+
@Override
|
59
|
+
public void handle(Buffer buffer) {
|
60
|
+
if (buf.isWritable(buffer.length()))
|
61
|
+
buf.writeBytes(buffer.getByteBuf());
|
62
|
+
else sock.pause();
|
63
|
+
}
|
64
|
+
});
|
65
|
+
|
66
|
+
this.sock.endHandler(new VoidHandler() {
|
67
|
+
@Override
|
68
|
+
protected void handle() {
|
69
|
+
eof.set(true);
|
70
|
+
}
|
71
|
+
});
|
72
|
+
}
|
73
|
+
|
74
|
+
/**
|
75
|
+
* Both of the calls block
|
76
|
+
*
|
77
|
+
* @param context
|
78
|
+
* @param args
|
79
|
+
* @return
|
80
|
+
*/
|
81
|
+
@JRubyMethod(name = {"read", "read_nonblock"}, required = 1, optional = 1)
|
82
|
+
public IRubyObject read(ThreadContext context, IRubyObject[] args) {
|
83
|
+
if (this.readClosed) throw context.runtime.newIOError("closed stream");
|
84
|
+
int length = RubyNumeric.num2int(args[0]);
|
85
|
+
byte[] data;
|
86
|
+
if (args.length == 1)
|
87
|
+
data = new byte[length];
|
88
|
+
else data = ((RubyString) args[1]).getBytes();
|
89
|
+
if (!(eof.get() || buf.isReadable())) {
|
90
|
+
this.buf.clear();
|
91
|
+
this.sock.resume();
|
92
|
+
}
|
93
|
+
waitReadable(this.buf);
|
94
|
+
while (!eof.get() && length > 0) {
|
95
|
+
int readedLength = Math.min(this.buf.readableBytes(), length);
|
96
|
+
this.buf.readBytes(data, this.buf.readerIndex(), readedLength);
|
97
|
+
length -= readedLength;
|
98
|
+
}
|
99
|
+
return context.runtime.newString(new ByteList(data));
|
100
|
+
}
|
101
|
+
|
102
|
+
/**
|
103
|
+
* Though required by rack spec to impelement write_nonblock, it's just easier to block both of the calls.
|
104
|
+
*
|
105
|
+
* @param context the calling threadcontext
|
106
|
+
* @param str the string to write to the underline stream
|
107
|
+
* @return the length written
|
108
|
+
*/
|
109
|
+
@JRubyMethod(name = {"write", "write_nonblock"}, required = 1)
|
110
|
+
public IRubyObject write(ThreadContext context, IRubyObject str) {
|
111
|
+
if (this.writeClosed) throw context.runtime.newIOError("closed stream");
|
112
|
+
RubyString data;
|
113
|
+
if (str instanceof RubyString)
|
114
|
+
data = (RubyString) str;
|
115
|
+
else
|
116
|
+
data = (RubyString) str.callMethod(context, "to_s");
|
117
|
+
if (this.sock.writeQueueFull())
|
118
|
+
waitWritable(this.sock);
|
119
|
+
this.sock.write(data.asJavaString());
|
120
|
+
// TODO return the length actually written
|
121
|
+
return data.length();
|
122
|
+
}
|
123
|
+
|
124
|
+
@JRubyMethod(name = "close_write")
|
125
|
+
public IRubyObject closeWrite(ThreadContext context) {
|
126
|
+
this.writeClosed = true;
|
127
|
+
return context.runtime.getTrue();
|
128
|
+
}
|
129
|
+
|
130
|
+
@JRubyMethod(name = "closeRead")
|
131
|
+
public IRubyObject closeRead(ThreadContext context) {
|
132
|
+
this.readClosed = true;
|
133
|
+
return context.runtime.getTrue();
|
134
|
+
}
|
135
|
+
|
136
|
+
@JRubyMethod(name = "close")
|
137
|
+
public IRubyObject close(ThreadContext context) {
|
138
|
+
this.sock.close();
|
139
|
+
this.closed = true;
|
140
|
+
return context.runtime.getTrue();
|
141
|
+
}
|
142
|
+
|
143
|
+
@JRubyMethod(name = "closed?")
|
144
|
+
public IRubyObject isClosed(ThreadContext context) {
|
145
|
+
return context.runtime.newBoolean(this.closed);
|
146
|
+
}
|
147
|
+
|
148
|
+
@JRubyMethod(name = "flush")
|
149
|
+
public IRubyObject flush(ThreadContext context) {
|
150
|
+
return context.runtime.getTrue();
|
151
|
+
}
|
152
|
+
|
153
|
+
private void waitWritable(WriteStream<?> stream) {
|
154
|
+
final AtomicBoolean writable = new AtomicBoolean(false);
|
155
|
+
stream.drainHandler(new Handler<Void>() {
|
156
|
+
@Override
|
157
|
+
public void handle(Void empty) {
|
158
|
+
writable.set(true);
|
159
|
+
}
|
160
|
+
});
|
161
|
+
while (!writable.get())
|
162
|
+
;
|
163
|
+
}
|
164
|
+
|
165
|
+
private void waitReadable(ByteBuf buf) {
|
166
|
+
while (!buf.isReadable())
|
167
|
+
;
|
168
|
+
}
|
169
|
+
}
|