jubilee 0.1.2

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.
Files changed (54) hide show
  1. data/.rbenv-version +1 -0
  2. data/Gemfile +16 -0
  3. data/Gemfile.lock +54 -0
  4. data/Guardfile +24 -0
  5. data/README.md +61 -0
  6. data/Rakefile +94 -0
  7. data/VERSION +1 -0
  8. data/bin/jubilee +6 -0
  9. data/bin/jubilee_d +10 -0
  10. data/examples/jubilee/keystore.jks +0 -0
  11. data/examples/jubilee/server-keystore.jks +0 -0
  12. data/examples/ssl/ServerTest.java +19 -0
  13. data/examples/ssl/webroot/index.html +10 -0
  14. data/jars/netty-3.6.0.Beta1.jar +0 -0
  15. data/jars/vertx-core-1.3.0.final.jar +0 -0
  16. data/java/.idea/ant.xml +7 -0
  17. data/java/.idea/libraries/jruby.xml +9 -0
  18. data/java/.idea/libraries/netty_3_6_0_Beta1.xml +9 -0
  19. data/java/.idea/libraries/vertx_core_1_3_0_final.xml +9 -0
  20. data/java/src/jubilee/JubileeService.java +21 -0
  21. data/java/src/org/jruby/jubilee/Const.java +148 -0
  22. data/java/src/org/jruby/jubilee/RackApplication.java +78 -0
  23. data/java/src/org/jruby/jubilee/RackEnvironment.java +13 -0
  24. data/java/src/org/jruby/jubilee/RackErrors.java +44 -0
  25. data/java/src/org/jruby/jubilee/RackInput.java +62 -0
  26. data/java/src/org/jruby/jubilee/RackResponse.java +16 -0
  27. data/java/src/org/jruby/jubilee/Server.java +104 -0
  28. data/java/src/org/jruby/jubilee/deploy/Starter.java +26 -0
  29. data/java/src/org/jruby/jubilee/impl/DefaultRackEnvironment.java +98 -0
  30. data/java/src/org/jruby/jubilee/impl/NullIO.java +111 -0
  31. data/java/src/org/jruby/jubilee/impl/RubyIORackErrors.java +68 -0
  32. data/java/src/org/jruby/jubilee/impl/RubyIORackInput.java +164 -0
  33. data/lib/jubilee.rb +11 -0
  34. data/lib/jubilee/application.rb +13 -0
  35. data/lib/jubilee/cli.rb +74 -0
  36. data/lib/jubilee/configuration.rb +52 -0
  37. data/lib/jubilee/const.rb +39 -0
  38. data/lib/jubilee/jubilee.jar +0 -0
  39. data/lib/jubilee/response.rb +64 -0
  40. data/lib/jubilee/server.rb +16 -0
  41. data/lib/rack/handler/jubilee.rb +43 -0
  42. data/test/.rbenv-version +1 -0
  43. data/test/config/app.rb +5 -0
  44. data/test/jubilee/test_cli.rb +11 -0
  45. data/test/jubilee/test_config.rb +14 -0
  46. data/test/jubilee/test_persistent.rb +238 -0
  47. data/test/jubilee/test_rack_server.rb +116 -0
  48. data/test/jubilee/test_server.rb +68 -0
  49. data/test/sinatra_app/app.rb +31 -0
  50. data/test/sinatra_app/config.ru +6 -0
  51. data/test/sinatra_app/public/test.html +10 -0
  52. data/test/sinatra_app/unicorn.conf.rb +29 -0
  53. data/test/test_helper.rb +21 -0
  54. metadata +160 -0
@@ -0,0 +1,78 @@
1
+ package org.jruby.jubilee;
2
+
3
+ import org.jruby.Ruby;
4
+ import org.jruby.javasupport.JavaEmbedUtils;
5
+ import org.jruby.jubilee.impl.DefaultRackEnvironment;
6
+ import org.jruby.jubilee.impl.RubyIORackInput;
7
+ import org.jruby.runtime.builtin.IRubyObject;
8
+ import org.vertx.java.core.Handler;
9
+ import org.vertx.java.core.SimpleHandler;
10
+ import org.vertx.java.core.Vertx;
11
+ import org.vertx.java.core.buffer.Buffer;
12
+ import org.vertx.java.core.http.HttpServerRequest;
13
+
14
+ import java.util.concurrent.*;
15
+
16
+ /**
17
+ * Created with IntelliJ IDEA.
18
+ * User: isaiah
19
+ * Date: 11/29/12
20
+ * Time: 5:40 PM
21
+ */
22
+ public class RackApplication {
23
+ private IRubyObject app;
24
+ private boolean ssl;
25
+ private Buffer bodyBuf;
26
+ private Vertx vertx;
27
+
28
+ private ExecutorService exec;
29
+
30
+ public RackApplication(Vertx vertx, IRubyObject app, boolean ssl) {
31
+ this.app = app;
32
+ this.ssl = ssl;
33
+ bodyBuf = new Buffer(0);
34
+ this.vertx = vertx;
35
+ exec = Executors.newCachedThreadPool();
36
+ }
37
+
38
+ public void call(final HttpServerRequest request) {
39
+ final Ruby runtime = app.getRuntime();
40
+ final CountDownLatch bodyLatch = new CountDownLatch(1);
41
+ request.dataHandler(new Handler<Buffer>() {
42
+ @Override
43
+ public void handle(Buffer buffer) {
44
+ bodyBuf.appendBuffer(buffer);
45
+ }
46
+ });
47
+ // TODO optimize by use NullIO when there is no body here.
48
+ Runnable task = new Runnable() {
49
+ @Override
50
+ public void run() {
51
+ RackInput input = new RubyIORackInput(runtime, bodyBuf, bodyLatch);
52
+ RackEnvironment env = new DefaultRackEnvironment(runtime, request, input, ssl);
53
+ IRubyObject result = app.callMethod(runtime.getCurrentContext(), "call", env.getEnv());
54
+ final RackResponse response = (RackResponse) JavaEmbedUtils.rubyToJava(runtime, result, RackResponse.class);
55
+ vertx.runOnLoop(new SimpleHandler() {
56
+ @Override
57
+ public void handle() {
58
+ response.respond(request.response);
59
+ }
60
+ });
61
+ }
62
+ };
63
+ exec.execute(task);
64
+ request.endHandler(new SimpleHandler() {
65
+ @Override
66
+ protected void handle() {
67
+ bodyLatch.countDown();
68
+ }
69
+ });
70
+ }
71
+
72
+ public void shutdown(boolean force) {
73
+ if (force)
74
+ exec.shutdownNow();
75
+ else
76
+ exec.shutdown();
77
+ }
78
+ }
@@ -0,0 +1,13 @@
1
+ package org.jruby.jubilee;
2
+
3
+ import org.jruby.RubyHash;
4
+
5
+ /**
6
+ * Created with IntelliJ IDEA.
7
+ * User: isaiah
8
+ * Date: 11/26/12
9
+ * Time: 11:33 AM
10
+ */
11
+ public interface RackEnvironment {
12
+ public RubyHash getEnv();
13
+ }
@@ -0,0 +1,44 @@
1
+ package org.jruby.jubilee;
2
+
3
+ /**
4
+ * Created with IntelliJ IDEA.
5
+ * User: isaiah
6
+ * Date: 11/26/12
7
+ * Time: 12:01 PM
8
+ */
9
+ import org.jruby.runtime.ThreadContext;
10
+ import org.jruby.runtime.builtin.IRubyObject;
11
+
12
+ public interface RackErrors {
13
+ /**
14
+ * puts must be called with a single argument that responds to to_s.
15
+ *
16
+ * @param context ruby context
17
+ * @param arg must respond to to_s
18
+ * @return nil
19
+ */
20
+ IRubyObject puts( ThreadContext context, IRubyObject arg );
21
+
22
+ /**
23
+ * write must be called with a single argument that is a String.
24
+ *
25
+ * @param context ruby context
26
+ * @param string a Ruby String
27
+ * @return nil
28
+ */
29
+ IRubyObject write( ThreadContext context, IRubyObject string );
30
+
31
+ /**
32
+ * flush must be called without arguments and must be called in order to make the error appear for sure.
33
+ *
34
+ * @return nil
35
+ */
36
+ IRubyObject flush();
37
+
38
+ /**
39
+ * close must never be called on the error stream.
40
+ *
41
+ * @return nil
42
+ */
43
+ IRubyObject close();
44
+ }
@@ -0,0 +1,62 @@
1
+ package org.jruby.jubilee;
2
+
3
+ import org.jruby.runtime.Block;
4
+ import org.jruby.runtime.ThreadContext;
5
+ import org.jruby.runtime.builtin.IRubyObject;
6
+
7
+ /**
8
+ * @author isaiah
9
+ * @since Nov 26, 2012
10
+ *
11
+ * Specification for Rack input, translated to a Java interface.
12
+ * @author nicksieger
13
+ */
14
+ public interface RackInput
15
+ {
16
+ /**
17
+ * gets must be called without arguments and return a string, or nil on EOF.
18
+ * @param context it's a JRuby thing
19
+ * @return a string, or nil on EOF
20
+ */
21
+ IRubyObject gets( ThreadContext context );
22
+
23
+ /**
24
+ * read behaves like IO#read. Its signature is read([length, [buffer]]). If given,
25
+ * length must be an non-negative Integer (>= 0) or nil, and buffer must be a
26
+ * String and may not be nil. If length is given and not nil, then this method
27
+ * reads at most length bytes from the input stream. If length is not given or
28
+ * nil, then this method reads all data until EOF. When EOF is reached, this
29
+ * method returns nil if length is given and not nil, or "" if length is not
30
+ * given or is nil. If buffer is given, then the read data will be placed into
31
+ * buffer instead of a newly created String object.
32
+ * @param context it's a JRuby thing
33
+ * @param args [length, [buffer]]
34
+ * @return nil if length is given and not nil, or "" if length is not given or nil
35
+ */
36
+ IRubyObject read( ThreadContext context, IRubyObject[] args );
37
+
38
+ /**
39
+ * each must be called without arguments and only yield Strings.
40
+ * @param context it's a JRuby thing
41
+ * @param block that receives yield of Strings
42
+ * @return pretty much nil
43
+ */
44
+ public IRubyObject each( ThreadContext context, Block block );
45
+
46
+ /**
47
+ * rewind must be called without arguments. It rewinds the input stream back
48
+ * to the beginning. It must not raise Errno::ESPIPE: that is, it may not be
49
+ * a pipe or a socket. Therefore, handler developers must buffer the input
50
+ * data into some rewindable object if the underlying input stream is not rewindable.
51
+ * @param context it's a JRuby thing
52
+ * @return pretty much nil
53
+ */
54
+ public IRubyObject rewind( ThreadContext context );
55
+
56
+ /**
57
+ * Close the input. Exposed only to the Java side because the Rack spec says
58
+ * that application code must not call close, so we don't expose a close method to Ruby.
59
+ */
60
+ public IRubyObject close(ThreadContext context);
61
+ }
62
+
@@ -0,0 +1,16 @@
1
+ package org.jruby.jubilee;
2
+
3
+ import org.jruby.runtime.builtin.IRubyObject;
4
+ import org.vertx.java.core.http.HttpServerResponse;
5
+
6
+ import java.util.Map;
7
+
8
+ /**
9
+ * Created with IntelliJ IDEA.
10
+ * User: isaiah
11
+ * Date: 11/29/12
12
+ * Time: 5:39 PM
13
+ */
14
+ public interface RackResponse {
15
+ public void respond(HttpServerResponse response);
16
+ }
@@ -0,0 +1,104 @@
1
+ package org.jruby.jubilee;
2
+
3
+ import org.vertx.java.core.*;
4
+ import org.vertx.java.core.http.*;
5
+
6
+ import org.jruby.*;
7
+ import org.jruby.runtime.*;
8
+ import org.jruby.runtime.builtin.*;
9
+ import org.jruby.anno.JRubyMethod;
10
+
11
+ public class Server extends RubyObject {
12
+ final private Vertx vertx;
13
+ final private HttpServer httpServer;
14
+ private RackApplication app;
15
+ private boolean running;
16
+ private boolean ssl = false;
17
+ private String keyStorePath;
18
+ private String keyStorePassword;
19
+ private int port;
20
+
21
+ public static void createServerClass(Ruby runtime) {
22
+ RubyModule mJubilee = runtime.defineModule("Jubilee");
23
+ RubyClass serverClass = mJubilee.defineClassUnder("VertxServer", runtime.getObject(), ALLOCATOR);
24
+ serverClass.defineAnnotatedMethods(Server.class);
25
+ }
26
+
27
+ private static ObjectAllocator ALLOCATOR = new ObjectAllocator() {
28
+ public IRubyObject allocate(Ruby ruby, RubyClass rubyClass) {
29
+ return new Server(ruby, rubyClass);
30
+ }
31
+ };
32
+
33
+ public Server(Ruby ruby, RubyClass rubyClass) {
34
+ super(ruby, rubyClass);
35
+ vertx = Vertx.newVertx();
36
+ httpServer = vertx.createHttpServer();
37
+ }
38
+
39
+ @JRubyMethod(name = "initialize", required = 2, optional = 3)
40
+ public IRubyObject initialize(ThreadContext context, IRubyObject[] args, Block block) {
41
+ this.port = RubyInteger.num2int(args[1]);
42
+ if (args.length > 2) {
43
+ this.ssl = args[2].isTrue();
44
+ }
45
+ this.app = new RackApplication(vertx, args[0], this.ssl);
46
+ if (args.length > 3)
47
+ this.keyStorePath = args[3].toString();
48
+ if (args.length > 4)
49
+ this.keyStorePassword = args[4].toString();
50
+ running = false;
51
+ return this;
52
+ }
53
+
54
+ @JRubyMethod(name = "start", optional = 1)
55
+ public IRubyObject start(final ThreadContext context, final IRubyObject[] args, final Block block) {
56
+ this.running = true;
57
+ httpServer.setAcceptBacklog(10000);
58
+ httpServer.requestHandler(new Handler<HttpServerRequest>() {
59
+ public void handle(final HttpServerRequest req) {
60
+ app.call(req);
61
+ }
62
+ });
63
+ if (ssl) httpServer.setSSL(true).setKeyStorePath(this.keyStorePath)
64
+ .setKeyStorePassword(this.keyStorePassword);
65
+ httpServer.listen(this.port);
66
+ return this;
67
+ }
68
+
69
+ /**
70
+ * Set timeout for keep alive connection
71
+ * @param context
72
+ * @param timeout (in TimeUnit.SECONDS)
73
+ * @return this
74
+ */
75
+ @JRubyMethod(name = "persistent_timeout=")
76
+ public IRubyObject setPersistentTimeout(final ThreadContext context, final IRubyObject timeout) {
77
+ httpServer.setPersistentTimeout(RubyInteger.fix2long(timeout) * 1000);
78
+ return this;
79
+ }
80
+
81
+ /**
82
+ * Stop the HttpServer
83
+ * @param context
84
+ * @param args if shutdown abruptly
85
+ * @param block callback on close
86
+ * @return
87
+ */
88
+ @JRubyMethod(name = {"stop", "close"}, optional = 1)
89
+ public IRubyObject close(ThreadContext context, IRubyObject[] args, Block block) {
90
+ if (running) {
91
+ if (args.length == 1)
92
+ app.shutdown(args[0].isTrue());
93
+ else
94
+ app.shutdown(false);
95
+
96
+ this.running = false;
97
+ httpServer.close();
98
+ if (block.isGiven()) block.yieldSpecific(context);
99
+ } else {
100
+ getRuntime().getOutputStream().println("jubilee server not running?");
101
+ }
102
+ return getRuntime().getNil();
103
+ }
104
+ }
@@ -0,0 +1,26 @@
1
+ package org.jruby.jubilee.deploy;
2
+
3
+ import java.util.concurrent.CountDownLatch;
4
+
5
+ /**
6
+ * Created with IntelliJ IDEA.
7
+ * User: isaiah
8
+ * Date: 12/3/12
9
+ * Time: 7:37 PM
10
+ */
11
+ public class Starter {
12
+ private CountDownLatch stopLatch = new CountDownLatch(1);
13
+
14
+ public void block() {
15
+ while(true) {
16
+ try {
17
+ stopLatch.await();
18
+ break;
19
+ } catch (InterruptedException ignore) {}
20
+ }
21
+ }
22
+
23
+ public void unblock() {
24
+ stopLatch.countDown();
25
+ }
26
+ }
@@ -0,0 +1,98 @@
1
+ package org.jruby.jubilee.impl;
2
+
3
+ import org.jruby.RubyArray;
4
+ import org.jruby.RubyString;
5
+ import org.jruby.jubilee.RackEnvironment;
6
+ import org.jruby.jubilee.Const;
7
+ import org.jruby.Ruby;
8
+ import org.jruby.RubyHash;
9
+ import org.jruby.jubilee.RackInput;
10
+ import org.vertx.java.core.http.HttpServerRequest;
11
+
12
+ import java.util.Map;
13
+
14
+ /**
15
+ * Created with IntelliJ IDEA.
16
+ * User: isaiah
17
+ * Date: 11/26/12
18
+ * Time: 11:40 AM
19
+ */
20
+ public class DefaultRackEnvironment implements RackEnvironment {
21
+ private RubyHash env;
22
+ private Map<String, String> headers;
23
+ private Ruby runtime;
24
+
25
+ public DefaultRackEnvironment(final Ruby runtime, final HttpServerRequest request, RackInput input, boolean isSSL) {
26
+
27
+ this.runtime = runtime;
28
+ // DEFAULT
29
+ env = RubyHash.newHash(runtime);
30
+ env.put(Const.RACK_VERSION, Const.RackVersion(runtime));
31
+ env.put(Const.RACK_ERRORS, new RubyIORackErrors(runtime));
32
+ env.put(Const.RACK_MULTITHREAD, true);
33
+ env.put(Const.RACK_MULTIPROCESS, false);
34
+ env.put(Const.RACK_RUNONCE, true);
35
+ if (isSSL)
36
+ env.put(Const.URL_SCHEME, Const.HTTPS);
37
+ else
38
+ env.put(Const.URL_SCHEME, Const.HTTP);
39
+ env.put(Const.SCRIPT_NAME, "");
40
+
41
+ env.put(Const.SERVER_PROTOCOL, Const.HTTP_11);
42
+ env.put(Const.SERVER_SOFTWARE, Const.JUBILEE_VERSION);
43
+ env.put(Const.GATEWAY_INTERFACE, Const.CGI_VER);
44
+
45
+ // Parse request headers
46
+ headers = request.headers();
47
+ String host;
48
+ if ((host = headers.get(Const.Vertx.HOST)) != null) {
49
+ int colon = host.indexOf(":");
50
+ if (colon > 0) {
51
+ env.put(Const.SERVER_NAME, host.substring(0, colon));
52
+ env.put(Const.SERVER_PORT, host.substring(colon + 1));
53
+ } else {
54
+ env.put(Const.SERVER_NAME, host);
55
+ env.put(Const.SERVER_PORT, Const.PORT_80);
56
+ }
57
+
58
+ } else {
59
+ env.put(Const.SERVER_NAME, Const.LOCALHOST);
60
+ env.put(Const.SERVER_PORT, Const.PORT_80);
61
+ }
62
+
63
+ env.put(Const.RACK_INPUT, input);
64
+ env.put(Const.REQUEST_METHOD, request.method);
65
+ env.put(Const.REQUEST_PATH, request.path);
66
+ env.put(Const.REQUEST_URI, request.uri);
67
+ env.put(Const.QUERY_STRING, orEmpty(request.query));
68
+ env.put(Const.REMOTE_ADDR, request.remoteAddr);
69
+ env.put(Const.HTTP_HOST, host);
70
+ env.put(Const.HTTP_COOKIE, orEmpty(headers.get(Const.Vertx.COOKIE)));
71
+ env.put(Const.HTTP_USER_AGENT, headers.get(Const.Vertx.USER_AGENT));
72
+ env.put(Const.HTTP_ACCEPT, headers.get(Const.Vertx.ACCEPT));
73
+ env.put(Const.HTTP_ACCEPT_LANGUAGE, orEmpty(headers.get(Const.Vertx.ACCEPT_LANGUAGE)));
74
+ env.put(Const.HTTP_ACCEPT_ENCODING, orEmpty(headers.get(Const.Vertx.ACCEPT_ENCODING)));
75
+ env.put(Const.HTTP_CONNECTION, orEmpty(headers.get(Const.Vertx.CONNECTION)));
76
+ env.put(Const.HTTP_CONTENT_TYPE, orEmpty(headers.get(Const.Vertx.CONTENT_TYPE)));
77
+ String contentLength;
78
+ if ((contentLength = headers.get(Const.Vertx.CONTENT_LENGTH)) != null)
79
+ env.put(Const.HTTP_CONTENT_LENGTH, contentLength);
80
+ env.put(Const.PATH_INFO, request.path);
81
+
82
+ // Additional headers
83
+ for (Map.Entry<String, String> var : Const.ADDITIONAL_HEADERS.entrySet())
84
+ setRackHeader(var.getKey(), var.getValue());
85
+ }
86
+
87
+ public RubyHash getEnv() {
88
+ return env;
89
+ }
90
+
91
+ private RubyString orEmpty(String jString) {
92
+ return jString == null ? RubyString.newEmptyString(runtime) : RubyString.newString(runtime, jString);
93
+ }
94
+
95
+ private void setRackHeader(String vertxHeader, String rackHeader) {
96
+ if (headers.containsKey(vertxHeader)) env.put(rackHeader, headers.get(vertxHeader));
97
+ }
98
+ }