jubilee 0.1.2

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