jubilee 2.1.0.Alpha1-java → 2.1.0.beta-java
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
}
|