jubilee 1.0.2 → 1.1.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (128) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -1
  3. data/CHANGELOG +17 -0
  4. data/Gemfile +19 -1
  5. data/Gemfile.lock +189 -31
  6. data/LICENSE.txt +20 -0
  7. data/README.md +44 -5
  8. data/ROADMAP +5 -0
  9. data/Rakefile +6 -5
  10. data/examples/chatapp/Gemfile +2 -1
  11. data/examples/chatapp/Gemfile.lock +8 -6
  12. data/examples/chatapp/README.md +5 -2
  13. data/examples/chatapp/app.rb +24 -0
  14. data/examples/chatapp/public/assets/javascripts/application.js +59 -6
  15. data/examples/chatapp/public/assets/stylesheets/application.css +25 -0
  16. data/examples/jubilee.conf.rb +0 -3
  17. data/jars/{hazelcast-2.6.jar → hazelcast-2.6.3.jar} +0 -0
  18. data/jars/{netty-all-4.0.4.Final.jar → netty-all-4.0.13.Final.jar} +0 -0
  19. data/jars/vertx-core-2.1M3-SNAPSHOT.jar +0 -0
  20. data/jars/vertx-hazelcast-2.1M3-SNAPSHOT.jar +0 -0
  21. data/java/src/jubilee/JubileeService.java +7 -7
  22. data/java/src/org/jruby/jubilee/Const.java +10 -35
  23. data/java/src/org/jruby/jubilee/RackApplication.java +77 -46
  24. data/java/src/org/jruby/jubilee/RackResponse.java +1 -3
  25. data/java/src/org/jruby/jubilee/RubyHttpServerResponse.java +88 -0
  26. data/java/src/org/jruby/jubilee/{Server.java → RubyServer.java} +17 -14
  27. data/java/src/org/jruby/jubilee/impl/RackEnvironment.java +157 -0
  28. data/java/src/org/jruby/jubilee/impl/RackEnvironmentHash.java +449 -0
  29. data/java/src/org/jruby/jubilee/impl/RubyIORackInput.java +5 -5
  30. data/java/src/org/jruby/jubilee/impl/{NullIO.java → RubyNullIO.java} +5 -9
  31. data/java/src/org/jruby/jubilee/utils/RubyHelper.java +37 -0
  32. data/jubilee.gemspec +101 -14
  33. data/lib/jubilee.rb +4 -3
  34. data/lib/jubilee/cli.rb +2 -1
  35. data/lib/jubilee/configuration.rb +4 -9
  36. data/lib/jubilee/const.rb +2 -2
  37. data/lib/jubilee/jubilee.jar +0 -0
  38. data/lib/jubilee/response.rb +9 -8
  39. data/lib/jubilee/server.rb +1 -1
  40. data/lib/jubilee/version.rb +8 -1
  41. data/lib/rack/chunked.rb +38 -0
  42. data/spec/apps/rack/basic/config.ru +50 -0
  43. data/spec/apps/rails4/basic/.gitignore +16 -0
  44. data/spec/apps/rails4/basic/Gemfile +41 -0
  45. data/spec/apps/rails4/basic/Gemfile.lock +127 -0
  46. data/spec/apps/rails4/basic/README.rdoc +28 -0
  47. data/spec/apps/rails4/basic/Rakefile +6 -0
  48. data/spec/apps/rails4/basic/app/assets/images/.keep +0 -0
  49. data/spec/apps/rails4/basic/app/assets/images/rails.png +0 -0
  50. data/spec/apps/rails4/basic/app/assets/javascripts/application.js +16 -0
  51. data/spec/apps/rails4/basic/app/assets/stylesheets/application.css +13 -0
  52. data/spec/apps/rails4/basic/app/controllers/application_controller.rb +5 -0
  53. data/spec/apps/rails4/basic/app/controllers/concerns/.keep +0 -0
  54. data/spec/apps/rails4/basic/app/controllers/reloader_controller.rb +11 -0
  55. data/spec/apps/rails4/basic/app/controllers/reloader_controller.rb.erb +11 -0
  56. data/spec/apps/rails4/basic/app/controllers/root_controller.rb +14 -0
  57. data/spec/apps/rails4/basic/app/helpers/application_helper.rb +2 -0
  58. data/spec/apps/rails4/basic/app/mailers/.keep +0 -0
  59. data/spec/apps/rails4/basic/app/models/.keep +0 -0
  60. data/spec/apps/rails4/basic/app/models/concerns/.keep +0 -0
  61. data/spec/apps/rails4/basic/app/views/layouts/application.html.erb +14 -0
  62. data/spec/apps/rails4/basic/app/views/reloader/index.html.erb +1 -0
  63. data/spec/apps/rails4/basic/app/views/root/index.html.erb +8 -0
  64. data/spec/apps/rails4/basic/app/views/root/streaming.html.erb +6 -0
  65. data/spec/apps/rails4/basic/bin/bundle +3 -0
  66. data/spec/apps/rails4/basic/bin/rails +4 -0
  67. data/spec/apps/rails4/basic/bin/rake +4 -0
  68. data/spec/apps/rails4/basic/config.ru +4 -0
  69. data/spec/apps/rails4/basic/config/application.rb +23 -0
  70. data/spec/apps/rails4/basic/config/boot.rb +4 -0
  71. data/spec/apps/rails4/basic/config/database.yml +20 -0
  72. data/spec/apps/rails4/basic/config/environment.rb +5 -0
  73. data/spec/apps/rails4/basic/config/environments/development.rb +29 -0
  74. data/spec/apps/rails4/basic/config/environments/production.rb +80 -0
  75. data/spec/apps/rails4/basic/config/environments/test.rb +36 -0
  76. data/spec/apps/rails4/basic/config/initializers/backtrace_silencers.rb +7 -0
  77. data/spec/apps/rails4/basic/config/initializers/filter_parameter_logging.rb +4 -0
  78. data/spec/apps/rails4/basic/config/initializers/inflections.rb +16 -0
  79. data/spec/apps/rails4/basic/config/initializers/mime_types.rb +5 -0
  80. data/spec/apps/rails4/basic/config/initializers/secret_token.rb +12 -0
  81. data/spec/apps/rails4/basic/config/initializers/session_store.rb +2 -0
  82. data/spec/apps/rails4/basic/config/initializers/wrap_parameters.rb +14 -0
  83. data/spec/apps/rails4/basic/config/locales/en.yml +23 -0
  84. data/spec/apps/rails4/basic/config/routes.rb +5 -0
  85. data/spec/apps/rails4/basic/db/seeds.rb +7 -0
  86. data/spec/apps/rails4/basic/lib/assets/.keep +0 -0
  87. data/spec/apps/rails4/basic/lib/tasks/.keep +0 -0
  88. data/spec/apps/rails4/basic/public/404.html +58 -0
  89. data/spec/apps/rails4/basic/public/422.html +58 -0
  90. data/spec/apps/rails4/basic/public/500.html +57 -0
  91. data/spec/apps/rails4/basic/public/favicon.ico +0 -0
  92. data/spec/apps/rails4/basic/public/robots.txt +5 -0
  93. data/spec/apps/rails4/basic/public/some_page.html +7 -0
  94. data/spec/apps/rails4/basic/test/controllers/.keep +0 -0
  95. data/spec/apps/rails4/basic/test/fixtures/.keep +0 -0
  96. data/spec/apps/rails4/basic/test/helpers/.keep +0 -0
  97. data/spec/apps/rails4/basic/test/integration/.keep +0 -0
  98. data/spec/apps/rails4/basic/test/mailers/.keep +0 -0
  99. data/spec/apps/rails4/basic/test/models/.keep +0 -0
  100. data/spec/apps/rails4/basic/test/test_helper.rb +15 -0
  101. data/spec/apps/rails4/basic/vendor/assets/javascripts/.keep +0 -0
  102. data/spec/apps/rails4/basic/vendor/assets/stylesheets/.keep +0 -0
  103. data/spec/apps/sinatra/basic/Gemfile +4 -0
  104. data/spec/apps/sinatra/basic/Gemfile.lock +20 -0
  105. data/spec/apps/sinatra/basic/basic.rb +27 -0
  106. data/spec/apps/sinatra/basic/config.ru +7 -0
  107. data/spec/apps/sinatra/basic/public/some_page.html +7 -0
  108. data/spec/apps/sinatra/basic/views/index.erb +4 -0
  109. data/spec/apps/sinatra/basic/views/posted.haml +2 -0
  110. data/spec/apps/sinatra/basic/views/poster.haml +4 -0
  111. data/spec/apps/sinatra/basic/views/request_mapping.haml +4 -0
  112. data/spec/integration/basic_rack_spec.rb +89 -0
  113. data/spec/integration/basic_rails4_spec.rb +64 -0
  114. data/spec/integration/basic_sinatra_spec.rb +80 -0
  115. data/spec/spec_helper.rb +13 -0
  116. data/test/jubilee/test_cli.rb +1 -1
  117. data/test/jubilee/test_configuration.rb +18 -1
  118. data/test/jubilee/test_rack_server.rb +7 -7
  119. data/test/jubilee/test_response.rb +35 -36
  120. data/test/jubilee/test_server.rb +1 -1
  121. data/test/jubilee/test_upload.rb +14 -11
  122. data/test/test_helper.rb +1 -0
  123. metadata +97 -18
  124. data/VERSION +0 -1
  125. data/jars/vertx-core-2.1.0-SNAPSHOT.jar +0 -0
  126. data/java/src/org/jruby/jubilee/RackErrors.java +0 -44
  127. data/java/src/org/jruby/jubilee/impl/DefaultRackEnvironment.java +0 -99
  128. data/java/src/org/jruby/jubilee/impl/RubyIORackErrors.java +0 -68
@@ -1,7 +1,5 @@
1
1
  package org.jruby.jubilee;
2
2
 
3
- import org.vertx.java.core.http.HttpServerResponse;
4
-
5
3
  /**
6
4
  * Created with IntelliJ IDEA.
7
5
  * User: isaiah
@@ -9,5 +7,5 @@ import org.vertx.java.core.http.HttpServerResponse;
9
7
  * Time: 5:39 PM
10
8
  */
11
9
  public interface RackResponse {
12
- public void respond(HttpServerResponse response);
10
+ public void respond(RubyHttpServerResponse response);
13
11
  }
@@ -0,0 +1,88 @@
1
+ package org.jruby.jubilee;
2
+
3
+ import org.jruby.*;
4
+ import org.jruby.anno.JRubyClass;
5
+ import org.jruby.anno.JRubyMethod;
6
+ import org.jruby.runtime.ObjectAllocator;
7
+ import org.jruby.runtime.ThreadContext;
8
+ import org.jruby.runtime.builtin.IRubyObject;
9
+ import org.vertx.java.core.http.HttpServerResponse;
10
+
11
+ import java.util.Arrays;
12
+
13
+ /**
14
+ * Created by isaiah on 21/12/2013.
15
+ */
16
+ @JRubyClass(name = "HttpServerResponse")
17
+ public class RubyHttpServerResponse extends RubyObject {
18
+ private HttpServerResponse resp;
19
+ private String lineSeparator;
20
+
21
+ public static RubyClass createHttpServerResponseClass(final Ruby runtime) {
22
+ RubyModule mJubilee = runtime.getOrCreateModule("Jubilee");
23
+ RubyClass klazz = mJubilee.defineClassUnder("HttpServerResponse", runtime.getObject(), new ObjectAllocator() {
24
+ @Override
25
+ public IRubyObject allocate(Ruby ruby, RubyClass rubyClass) {
26
+ return new RubyHttpServerResponse(ruby, rubyClass);
27
+ }
28
+ });
29
+ klazz.defineAnnotatedMethods(RubyHttpServerResponse.class);
30
+ return klazz;
31
+ }
32
+
33
+ public RubyHttpServerResponse(Ruby ruby, RubyClass rubyClass) {
34
+ super(ruby, rubyClass);
35
+ }
36
+
37
+ public RubyHttpServerResponse(Ruby ruby, RubyClass rubyClass, HttpServerResponse resp) {
38
+ super(ruby, rubyClass);
39
+ this.resp = resp;
40
+ this.lineSeparator = System.getProperty("line.separator");
41
+ }
42
+
43
+ @JRubyMethod
44
+ public IRubyObject write(ThreadContext context, IRubyObject string) {
45
+ this.resp.write(string.asJavaString());
46
+ return context.runtime.getNil();
47
+ }
48
+
49
+ @JRubyMethod(name = "status_code=")
50
+ public IRubyObject setStatusCode(ThreadContext context, IRubyObject statusCode) {
51
+ this.resp.setStatusCode(RubyNumeric.num2int(statusCode));
52
+ return context.runtime.getNil();
53
+ }
54
+
55
+ @JRubyMethod(name = "chunked=")
56
+ public IRubyObject setChunked(ThreadContext context, IRubyObject chunked) {
57
+ this.resp.setChunked(chunked.isTrue());
58
+ return context.runtime.getNil();
59
+ }
60
+
61
+ @JRubyMethod(name = "put_header")
62
+ public IRubyObject putHeader(ThreadContext context, IRubyObject key, IRubyObject val) {
63
+ String cookie = val.asJavaString();
64
+ if (cookie.indexOf(this.lineSeparator) != -1)
65
+ this.resp.putHeader(key.asJavaString(),
66
+ Arrays.asList(val.asJavaString().split(this.lineSeparator)));
67
+ else this.resp.putHeader(key.asJavaString(), val.asJavaString());
68
+ return context.runtime.getNil();
69
+ }
70
+
71
+ @JRubyMethod(name = "send_file")
72
+ public IRubyObject sendFile(ThreadContext context, IRubyObject filePath) {
73
+ this.resp.sendFile(filePath.asJavaString());
74
+ return context.runtime.getNil();
75
+ }
76
+
77
+ @JRubyMethod
78
+ public IRubyObject end(ThreadContext context) {
79
+ this.resp.end();
80
+ return context.runtime.getNil();
81
+ }
82
+
83
+ @JRubyMethod(name = "put_default_headers")
84
+ public IRubyObject putDefaultHeaders(ThreadContext context) {
85
+ this.resp.putHeader("Server", Const.JUBILEE_VERSION);
86
+ return context.runtime.getNil();
87
+ }
88
+ }
@@ -14,7 +14,9 @@ import org.vertx.java.core.http.HttpServerRequest;
14
14
  import org.vertx.java.core.json.JsonArray;
15
15
  import org.vertx.java.core.json.JsonObject;
16
16
 
17
- public class Server extends RubyObject {
17
+ import java.io.IOException;
18
+
19
+ public class RubyServer extends RubyObject {
18
20
  private Vertx vertx;
19
21
  private HttpServer httpServer;
20
22
  private RackApplication app;
@@ -23,7 +25,6 @@ public class Server extends RubyObject {
23
25
  private String keyStorePath;
24
26
  private String keyStorePassword;
25
27
  private String eventBusPrefix;
26
- private int numberOfWorkers;
27
28
  private int port;
28
29
  private String host;
29
30
  private int clusterPort;
@@ -32,16 +33,16 @@ public class Server extends RubyObject {
32
33
  public static void createServerClass(Ruby runtime) {
33
34
  RubyModule mJubilee = runtime.defineModule("Jubilee");
34
35
  RubyClass serverClass = mJubilee.defineClassUnder("VertxServer", runtime.getObject(), ALLOCATOR);
35
- serverClass.defineAnnotatedMethods(Server.class);
36
+ serverClass.defineAnnotatedMethods(RubyServer.class);
36
37
  }
37
38
 
38
39
  private static ObjectAllocator ALLOCATOR = new ObjectAllocator() {
39
40
  public IRubyObject allocate(Ruby ruby, RubyClass rubyClass) {
40
- return new Server(ruby, rubyClass);
41
+ return new RubyServer(ruby, rubyClass);
41
42
  }
42
43
  };
43
44
 
44
- public Server(Ruby ruby, RubyClass rubyClass) {
45
+ public RubyServer(Ruby ruby, RubyClass rubyClass) {
45
46
  super(ruby, rubyClass);
46
47
  }
47
48
 
@@ -49,7 +50,8 @@ public class Server extends RubyObject {
49
50
  * Initialize jubilee server, take a rack application and a configuration hash as parameter
50
51
  *
51
52
  * @param context
52
- * @param args
53
+ * @param app
54
+ * @param config
53
55
  * @param block
54
56
  * @return
55
57
  */
@@ -66,19 +68,16 @@ public class Server extends RubyObject {
66
68
  RubySymbol keystore_path_k = runtime.newSymbol("keystore_path");
67
69
  RubySymbol keystore_password_k = runtime.newSymbol("keystore_password");
68
70
  RubySymbol eventbus_prefix_k = runtime.newSymbol("eventbus_prefix");
69
- RubySymbol number_of_workers_k = runtime.newSymbol("number_of_workers");
70
71
 
71
72
  /* retrieve from passed in options */
72
73
  this.port = Integer.parseInt(options.op_aref(context, port_k).toString());
73
74
  this.host = options.op_aref(context, host_k).toString();
74
75
 
75
76
  this.ssl = options.op_aref(context, ssl_k).isTrue();
76
- this.numberOfWorkers = Integer.parseInt(options.op_aref(context, number_of_workers_k).toString());
77
77
  if (options.has_key_p(keystore_path_k).isTrue()) {
78
78
  this.keyStorePath = options.op_aref(context, keystore_path_k).toString();
79
79
  this.keyStorePassword = options.op_aref(context, keystore_password_k).toString();
80
80
  }
81
- this.app = new RackApplication(app, this.ssl, this.numberOfWorkers);
82
81
  if (options.has_key_p(eventbus_prefix_k).isTrue())
83
82
  this.eventBusPrefix = options.op_aref(context, eventbus_prefix_k).toString();
84
83
 
@@ -95,6 +94,12 @@ public class Server extends RubyObject {
95
94
  }
96
95
 
97
96
  httpServer = vertx.createHttpServer();
97
+ try {
98
+ this.app = new RackApplication(vertx, context, app, this.ssl);
99
+ if (block.isGiven()) block.yieldSpecific(context, this);
100
+ } catch (IOException e) {
101
+ // noop
102
+ }
98
103
  return this;
99
104
  }
100
105
 
@@ -124,6 +129,7 @@ public class Server extends RubyObject {
124
129
  .setKeyStorePassword(this.keyStorePassword);
125
130
  httpServer.listen(this.port, this.host);
126
131
  this.running = true;
132
+ if (block.isGiven()) block.yieldSpecific(context, this);
127
133
  return this;
128
134
  }
129
135
 
@@ -152,13 +158,10 @@ public class Server extends RubyObject {
152
158
  @JRubyMethod(name = {"stop", "close"}, optional = 1)
153
159
  public IRubyObject close(ThreadContext context, IRubyObject[] args, Block block) {
154
160
  if (running) {
155
- if (args.length == 1)
156
- app.shutdown(args[0].isTrue());
157
- else
158
- app.shutdown(false);
159
-
160
161
  this.running = false;
161
162
  httpServer.close();
163
+ // DO I need to stop?
164
+ //vertx.stop();
162
165
  if (block.isGiven()) block.yieldSpecific(context);
163
166
  } else {
164
167
  getRuntime().getOutputStream().println("jubilee server not running?");
@@ -0,0 +1,157 @@
1
+ package org.jruby.jubilee.impl;
2
+
3
+ import io.netty.handler.codec.http.HttpHeaders;
4
+ import org.jruby.Ruby;
5
+ import org.jruby.RubyArray;
6
+ import org.jruby.RubyBoolean;
7
+ import org.jruby.RubyFixnum;
8
+ import org.jruby.RubyHash;
9
+ import org.jruby.RubyIO;
10
+ import org.jruby.RubyString;
11
+
12
+ import org.jruby.jubilee.Const;
13
+ import org.jruby.jubilee.RackInput;
14
+ import org.jruby.jubilee.utils.RubyHelper;
15
+ import org.vertx.java.core.MultiMap;
16
+ import org.vertx.java.core.http.HttpServerRequest;
17
+ import org.vertx.java.core.http.HttpVersion;
18
+
19
+ import java.io.IOException;
20
+ import java.net.InetAddress;
21
+ import java.net.InetSocketAddress;
22
+ import java.util.HashMap;
23
+ import java.util.Map;
24
+
25
+ public class RackEnvironment {
26
+
27
+ // When adding a key to the enum be sure to add its RubyString equivalent
28
+ // to populateRackKeyMap below
29
+ static enum RACK_KEY {
30
+ RACK_INPUT, RACK_ERRORS, REQUEST_METHOD, SCRIPT_NAME,
31
+ PATH_INFO, QUERY_STRING, SERVER_NAME, SERVER_PORT,
32
+ CONTENT_TYPE, REQUEST_URI, REMOTE_ADDR, URL_SCHEME,
33
+ VERSION, MULTITHREAD, MULTIPROCESS, RUN_ONCE, CONTENT_LENGTH,
34
+ HTTPS, HTTP_VERSION
35
+ }
36
+ static final int NUM_RACK_KEYS = RACK_KEY.values().length;
37
+
38
+ public RackEnvironment(final Ruby runtime) throws IOException {
39
+ this.runtime = runtime;
40
+ rackVersion = RubyArray.newArray(runtime, RubyFixnum.one(runtime), RubyFixnum.four(runtime));
41
+ errors = new RubyIO(runtime, runtime.getErr());
42
+ errors.setAutoclose(false);
43
+
44
+ populateRackKeyMap();
45
+ }
46
+
47
+ private void populateRackKeyMap() {
48
+ putRack("rack.input", RACK_KEY.RACK_INPUT);
49
+ putRack("rack.errors", RACK_KEY.RACK_ERRORS);
50
+ putRack("REQUEST_METHOD", RACK_KEY.REQUEST_METHOD);
51
+ putRack("SCRIPT_NAME", RACK_KEY.SCRIPT_NAME);
52
+ putRack("PATH_INFO", RACK_KEY.PATH_INFO);
53
+ putRack("QUERY_STRING", RACK_KEY.QUERY_STRING);
54
+ putRack("SERVER_NAME", RACK_KEY.SERVER_NAME);
55
+ putRack("SERVER_PORT", RACK_KEY.SERVER_PORT);
56
+ putRack("HTTP_VERSION", RACK_KEY.HTTP_VERSION);
57
+ putRack("CONTENT_TYPE", RACK_KEY.CONTENT_TYPE);
58
+ putRack("REQUEST_URI", RACK_KEY.REQUEST_URI);
59
+ putRack("REMOTE_ADDR", RACK_KEY.REMOTE_ADDR);
60
+ putRack("rack.url_scheme", RACK_KEY.URL_SCHEME);
61
+ putRack("rack.version", RACK_KEY.VERSION);
62
+ putRack("rack.multithread", RACK_KEY.MULTITHREAD);
63
+ putRack("rack.multiprocess", RACK_KEY.MULTIPROCESS);
64
+ putRack("rack.run_once", RACK_KEY.RUN_ONCE);
65
+ putRack("CONTENT_LENGTH", RACK_KEY.CONTENT_LENGTH);
66
+ putRack("HTTPS", RACK_KEY.HTTPS);
67
+ }
68
+
69
+ private void putRack(String key, RACK_KEY value) {
70
+ rackKeyMap.put(RubyHelper.toUsAsciiRubyString(runtime, key), value);
71
+ }
72
+
73
+ public RubyHash getEnv(final HttpServerRequest request,
74
+ final RackInput input,
75
+ final boolean isSSL) throws IOException {
76
+ // XXX
77
+ String contextPath = "/";
78
+ MultiMap headers = request.headers();
79
+ // TODO: Should we only use this faster RackEnvironmentHash if we detect
80
+ // specific JRuby versions that we know are compatible?
81
+ final RackEnvironmentHash env = new RackEnvironmentHash(runtime, headers, rackKeyMap);
82
+ env.lazyPut(RACK_KEY.RACK_INPUT, input, false);
83
+ env.lazyPut(RACK_KEY.RACK_ERRORS, errors, false);
84
+
85
+ // Don't use request.getPathInfo because that gets decoded by the container
86
+ String pathInfo = request.path();
87
+
88
+ // strip contextPath and servletPath from pathInfo
89
+ if (pathInfo.startsWith(contextPath) && !contextPath.equals("/")) {
90
+ pathInfo = pathInfo.substring(contextPath.length());
91
+ }
92
+
93
+ String scriptName = contextPath;
94
+ // SCRIPT_NAME should be an empty string for the root
95
+ if (scriptName.equals("/")) {
96
+ scriptName = "";
97
+ }
98
+
99
+ env.lazyPut(RACK_KEY.REQUEST_METHOD, request.method(), true);
100
+ env.lazyPut(RACK_KEY.SCRIPT_NAME, scriptName, false);
101
+ env.lazyPut(RACK_KEY.PATH_INFO, pathInfo, false);
102
+ env.lazyPut(RACK_KEY.QUERY_STRING, orEmpty(request.query()), false);
103
+ env.lazyPut(RACK_KEY.SERVER_NAME, Const.LOCALHOST, false);
104
+ env.lazyPut(RACK_KEY.SERVER_PORT, Const.PORT_80, true);
105
+ env.lazyPut(RACK_KEY.HTTP_VERSION,
106
+ request.version() == HttpVersion.HTTP_1_1 ? Const.HTTP_11 : Const.HTTP_10, true);
107
+ env.lazyPut(RACK_KEY.CONTENT_TYPE, headers.get(HttpHeaders.Names.CONTENT_TYPE), true);
108
+ env.lazyPut(RACK_KEY.REQUEST_URI, scriptName + pathInfo, false);
109
+ env.lazyPut(RACK_KEY.REMOTE_ADDR, getRemoteAddr(request), true);
110
+ env.lazyPut(RACK_KEY.URL_SCHEME, isSSL? Const.HTTPS : Const.HTTP, true);
111
+ env.lazyPut(RACK_KEY.VERSION, rackVersion, false);
112
+ env.lazyPut(RACK_KEY.MULTITHREAD, runtime.getTrue(), false);
113
+ env.lazyPut(RACK_KEY.MULTIPROCESS, runtime.getFalse(), false);
114
+ env.lazyPut(RACK_KEY.RUN_ONCE, runtime.getFalse(), false);
115
+
116
+
117
+ final int contentLength = getContentLength(headers);
118
+ if (contentLength >= 0) {
119
+ env.lazyPut(RACK_KEY.CONTENT_LENGTH, contentLength + "", true);
120
+ }
121
+
122
+ if (isSSL) {
123
+ env.lazyPut(RACK_KEY.HTTPS, "on", true);
124
+ }
125
+
126
+ return env;
127
+ }
128
+
129
+ private static String getRemoteAddr(final HttpServerRequest request) {
130
+ InetSocketAddress sourceAddress = request.remoteAddress();
131
+ if(sourceAddress == null) {
132
+ return "";
133
+ }
134
+ InetAddress address = sourceAddress.getAddress();
135
+ if(address == null) {
136
+ return "";
137
+ }
138
+ return address.getHostAddress();
139
+ }
140
+
141
+ private static int getContentLength(final MultiMap headers) {
142
+ final String contentLengthStr = headers.get(HttpHeaders.Names.CONTENT_LENGTH);
143
+ if (contentLengthStr == null || contentLengthStr.isEmpty()) {
144
+ return -1;
145
+ }
146
+ return Integer.parseInt(contentLengthStr);
147
+ }
148
+
149
+ private String orEmpty(String val) {
150
+ return val == null ? "" : val;
151
+ }
152
+
153
+ private final Ruby runtime;
154
+ private final RubyArray rackVersion;
155
+ private final RubyIO errors;
156
+ private final Map<RubyString, RACK_KEY> rackKeyMap = new HashMap<>();
157
+ }
@@ -0,0 +1,449 @@
1
+ package org.jruby.jubilee.impl;
2
+
3
+ import io.netty.handler.codec.http.HttpHeaders;
4
+ import org.jruby.Ruby;
5
+ import org.jruby.RubyArray;
6
+ import org.jruby.RubyBoolean;
7
+ import org.jruby.RubyFixnum;
8
+ import org.jruby.RubyHash;
9
+ import org.jruby.RubyString;
10
+ import org.jruby.runtime.Block;
11
+ import org.jruby.runtime.ThreadContext;
12
+ import org.jruby.runtime.builtin.IRubyObject;
13
+ import org.jruby.jubilee.utils.RubyHelper;
14
+ import org.vertx.java.core.MultiMap;
15
+
16
+ import java.util.HashMap;
17
+ import java.util.List;
18
+ import java.util.Map;
19
+
20
+ public class RackEnvironmentHash extends RubyHash {
21
+
22
+ public RackEnvironmentHash(final Ruby runtime, final MultiMap headers,
23
+ final Map<RubyString, RackEnvironment.RACK_KEY> rackKeyMap) {
24
+ super(runtime);
25
+ this.headers = headers;
26
+ this.rackKeyMap = rackKeyMap;
27
+ this.headerKeyMap = new HashMap<>();
28
+ }
29
+
30
+ public void lazyPut(RackEnvironment.RACK_KEY rackKey, final Object value, boolean usAscii) {
31
+ rackValues[rackKey.ordinal()] = value;
32
+ usAsciiValues[rackKey.ordinal()] = usAscii;
33
+ }
34
+
35
+ // synchronized probably isn't needed here since we create a new RackEnvironment
36
+ // per request, but we can't guarantee users aren't spawning a new thread and
37
+ // passing the env to that new thread
38
+ private synchronized void fillKey(final IRubyObject rubyKey) {
39
+ if (!filledEntireHash) {
40
+ if (rubyKey instanceof RubyString && !containsKey(rubyKey)) {
41
+ if (! filledHeaderKeyMap) populateHeaderKeyMap();
42
+ byte[] keyBytes = ((RubyString) rubyKey).getBytes();
43
+ if (keyBytes.length > 5 && keyBytes[0] == 'H'
44
+ && keyBytes[1] == 'T' && keyBytes[2] == 'T'
45
+ && keyBytes[3] == 'P' && keyBytes[4] == '_') {
46
+
47
+ fillHeaderKey((RubyString) rubyKey);
48
+ } else {
49
+ fillRackKey((RubyString) rubyKey);
50
+ }
51
+ }
52
+ }
53
+ }
54
+ private synchronized void fillEntireHash() {
55
+ if (!filledEntireHash) {
56
+ for (RubyString key : rackKeyMap.keySet()) {
57
+ fillRackKey(key);
58
+ }
59
+
60
+ for (String headerKey : headers.names()) {
61
+ RubyString rubyKey = RubyHelper.toUsAsciiRubyString(getRuntime(), rackHeaderNameToBytes(headerKey));
62
+ putHeaderKey(rubyKey, headerKey);
63
+ fillHeaderKey(rubyKey, headerKey);
64
+ }
65
+ filledEntireHash = true;
66
+ }
67
+ }
68
+ private synchronized void fillRackKey(final RubyString key) {
69
+ RackEnvironment.RACK_KEY rackKey = rackKeyMap.get(key);
70
+ if (rackKey != null) {
71
+ Object value = rackValues[rackKey.ordinal()];
72
+ if (value != null) {
73
+ if (value instanceof String) {
74
+ boolean usAscii = usAsciiValues[rackKey.ordinal()];
75
+ RubyString rubyValue = usAscii ? RubyHelper.toUsAsciiRubyString(getRuntime(), (String) value) :
76
+ RubyHelper.toUnicodeRubyString(getRuntime(), (String) value);
77
+ put(key, rubyValue);
78
+ } else {
79
+ put(key, value);
80
+ }
81
+ rackValues[rackKey.ordinal()] = null;
82
+ }
83
+ }
84
+ }
85
+
86
+ private synchronized void populateHeaderKeyMap() {
87
+ for (String key : headers.names()) {
88
+ byte[] rubyKeyBytes = rackHeaderNameToBytes(key);
89
+ RubyString rubyKey = RubyHelper.toUsAsciiRubyString(getRuntime(), rubyKeyBytes);
90
+ putHeaderKey(rubyKey, key);
91
+ }
92
+ this.filledHeaderKeyMap = true;
93
+ }
94
+
95
+ private void putHeaderKey(RubyString rubyKey, String key) {
96
+ this.headerKeyMap.put(rubyKey, key);
97
+ }
98
+ private synchronized void fillHeaderKey(final RubyString rubyKey) {
99
+ String headerKey = this.headerKeyMap.get(rubyKey);
100
+ if (headerKey != null) fillHeaderKey(rubyKey, headerKey);
101
+ }
102
+ private synchronized void fillHeaderKey(final RubyString rubyKey, String key) {
103
+ // RACK spec says not to create HTTP_CONTENT_TYPE or HTTP_CONTENT_LENGTH headers
104
+ if (!key.equals(HttpHeaders.Names.CONTENT_TYPE) && !key.equals(HttpHeaders.Names.CONTENT_LENGTH)) {
105
+ List<String> headerValues = headers.getAll(key);
106
+ if (!headerValues.isEmpty()) {
107
+ String headerValue = headerValues.get(0);
108
+ int valueIndex = 1;
109
+ while (valueIndex < headerValues.size()) {
110
+ headerValue += "\n" + headerValues.get(valueIndex++);
111
+ }
112
+ put(rubyKey, RubyHelper.toUnicodeRubyString(getRuntime(), headerValue));
113
+ }
114
+ }
115
+ }
116
+
117
+ private static byte[] rackHeaderNameToBytes(final String headerName) {
118
+ // This is a more performant implemention of:
119
+ // "HTTP_" + headerName.toUpperCase().replace('-', '_');
120
+ byte[] envNameBytes = new byte[headerName.length() + 5];
121
+ envNameBytes[0] = 'H';
122
+ envNameBytes[1] = 'T';
123
+ envNameBytes[2] = 'T';
124
+ envNameBytes[3] = 'P';
125
+ envNameBytes[4] = '_';
126
+ for (int i = 5; i < envNameBytes.length; i++) {
127
+ envNameBytes[i] = (byte) rackHeaderize(headerName.charAt(i - 5));
128
+ }
129
+ return envNameBytes;
130
+ }
131
+
132
+ private static char rackHeaderize(char c) {
133
+ if (c == '-') {
134
+ c = '_';
135
+ }
136
+ return toUpperCase(c);
137
+ }
138
+
139
+ private static char toUpperCase(char c) {
140
+ if (c >= 'a' && c <= 'z') {
141
+ c -= 32;
142
+ }
143
+ return c;
144
+ }
145
+
146
+ private final Object[] rackValues = new Object[RackEnvironment.NUM_RACK_KEYS];
147
+ private final boolean[] usAsciiValues = new boolean[RackEnvironment.NUM_RACK_KEYS];
148
+ private final MultiMap headers;
149
+ private final Map<RubyString, RackEnvironment.RACK_KEY> rackKeyMap;
150
+ private final Map<RubyString, String> headerKeyMap;
151
+ private boolean filledEntireHash = false;
152
+ private boolean filledHeaderKeyMap = false;
153
+
154
+
155
+
156
+ //
157
+ // Overridden RubyHash methods that operate on individual keys
158
+ //
159
+ @Override
160
+ public IRubyObject op_aref(ThreadContext context, IRubyObject key) {
161
+ fillKey(key);
162
+ return super.op_aref(context, key);
163
+ }
164
+ @Override
165
+ public IRubyObject fetch(ThreadContext context, IRubyObject key, Block block) {
166
+ fillKey(key);
167
+ return super.fetch(context, key, block);
168
+ }
169
+ @Override
170
+ public IRubyObject fetch(ThreadContext context, IRubyObject key, IRubyObject _default, Block block) {
171
+ fillKey(key);
172
+ return super.fetch(context, key ,_default, block);
173
+ }
174
+ @Override
175
+ public RubyBoolean has_key_p(IRubyObject key) {
176
+ fillKey(key);
177
+ return super.has_key_p(key);
178
+ }
179
+ @Override
180
+ public IRubyObject op_aset(ThreadContext context, IRubyObject key, IRubyObject value) {
181
+ fillKey(key);
182
+ return super.op_aset(context, key, value);
183
+ }
184
+ @Override
185
+ public IRubyObject delete(ThreadContext context, IRubyObject key, Block block) {
186
+ fillKey(key);
187
+ return super.delete(context, key, block);
188
+ }
189
+
190
+ //
191
+ // Overridden RubyHash methods that don't operate on individual keys so we
192
+ // fill the entire hash
193
+ //
194
+ @Override
195
+ public IRubyObject inspect(ThreadContext context) {
196
+ fillEntireHash();
197
+ return super.inspect(context);
198
+ }
199
+ @Override
200
+ public IRubyObject inspect19(ThreadContext context) {
201
+ fillEntireHash();
202
+ return super.inspect19(context);
203
+ }
204
+ @Override
205
+ public RubyFixnum rb_size() {
206
+ fillEntireHash();
207
+ return super.rb_size();
208
+ }
209
+ @Override
210
+ public RubyBoolean empty_p() {
211
+ fillEntireHash();
212
+ return super.empty_p();
213
+ }
214
+ @Override
215
+ public RubyArray to_a() {
216
+ fillEntireHash();
217
+ return super.to_a();
218
+ }
219
+ @Override
220
+ public IRubyObject to_s(ThreadContext context) {
221
+ fillEntireHash();
222
+ return super.to_s(context);
223
+ }
224
+ @Override
225
+ public IRubyObject to_s19(ThreadContext context) {
226
+ fillEntireHash();
227
+ return super.to_s19(context);
228
+ }
229
+ @Override
230
+ public RubyHash rehash() {
231
+ fillEntireHash();
232
+ return super.rehash();
233
+ }
234
+ @Override
235
+ public IRubyObject op_equal(final ThreadContext context, IRubyObject other) {
236
+ fillEntireHash();
237
+ return super.op_equal(context, other);
238
+ }
239
+ @Override
240
+ public IRubyObject op_eql19(final ThreadContext context, IRubyObject other) {
241
+ fillEntireHash();
242
+ return super.op_eql19(context, other);
243
+ }
244
+ @Override
245
+ public RubyFixnum hash() {
246
+ fillEntireHash();
247
+ return super.hash();
248
+ }
249
+ @Override
250
+ public RubyFixnum hash19() {
251
+ fillEntireHash();
252
+ return super.hash19();
253
+ }
254
+ @Override
255
+ public IRubyObject fetch(ThreadContext context, IRubyObject[] args, Block block) {
256
+ fillEntireHash();
257
+ return super.fetch(context, args, block);
258
+ }
259
+ @Override
260
+ public RubyBoolean has_value_p(ThreadContext context, IRubyObject expected) {
261
+ fillEntireHash();
262
+ return super.has_value_p(context, expected);
263
+ }
264
+ @Override
265
+ public IRubyObject each(final ThreadContext context, final Block block) {
266
+ fillEntireHash();
267
+ return super.each(context, block);
268
+ }
269
+ @Override
270
+ public IRubyObject each19(final ThreadContext context, final Block block) {
271
+ fillEntireHash();
272
+ return super.each19(context, block);
273
+ }
274
+ @Override
275
+ public IRubyObject each_value(final ThreadContext context, final Block block) {
276
+ fillEntireHash();
277
+ return super.each_value(context, block);
278
+ }
279
+ @Override
280
+ public IRubyObject each_key(final ThreadContext context, final Block block) {
281
+ fillEntireHash();
282
+ return super.each_key(context, block);
283
+ }
284
+ @Override
285
+ public IRubyObject select_bang(final ThreadContext context, final Block block) {
286
+ fillEntireHash();
287
+ return super.select_bang(context, block);
288
+ }
289
+ @Override
290
+ public IRubyObject keep_if(final ThreadContext context, final Block block) {
291
+ fillEntireHash();
292
+ return super.keep_if(context, block);
293
+ }
294
+ @Override
295
+ public IRubyObject sort(ThreadContext context, Block block) {
296
+ fillEntireHash();
297
+ return super.sort(context, block);
298
+ }
299
+ @Override
300
+ public IRubyObject index(ThreadContext context, IRubyObject expected) {
301
+ fillEntireHash();
302
+ return super.index(context, expected);
303
+ }
304
+ @Override
305
+ public IRubyObject index19(ThreadContext context, IRubyObject expected) {
306
+ fillEntireHash();
307
+ return super.index19(context, expected);
308
+ }
309
+ @Override
310
+ public IRubyObject key(ThreadContext context, IRubyObject expected) {
311
+ fillEntireHash();
312
+ return super.key(context, expected);
313
+ }
314
+ @Override
315
+ public RubyArray indices(ThreadContext context, IRubyObject[] indices) {
316
+ fillEntireHash();
317
+ return super.indices(context, indices);
318
+ }
319
+ @Override
320
+ public RubyArray keys() {
321
+ fillEntireHash();
322
+ return super.keys();
323
+ }
324
+ @Override
325
+ public RubyArray rb_values() {
326
+ fillEntireHash();
327
+ return super.rb_values();
328
+ }
329
+ @Override
330
+ public IRubyObject shift(ThreadContext context) {
331
+ fillEntireHash();
332
+ return super.shift(context);
333
+ }
334
+ @Override
335
+ public IRubyObject select(final ThreadContext context, final Block block) {
336
+ fillEntireHash();
337
+ return super.select(context, block);
338
+ }
339
+ @Override
340
+ public IRubyObject select19(final ThreadContext context, final Block block) {
341
+ fillEntireHash();
342
+ return super.select19(context, block);
343
+ }
344
+ @Override
345
+ public IRubyObject delete_if(final ThreadContext context, final Block block) {
346
+ fillEntireHash();
347
+ return super.delete_if(context, block);
348
+ }
349
+ @Override
350
+ public IRubyObject reject(final ThreadContext context, final Block block) {
351
+ fillEntireHash();
352
+ return super.reject(context, block);
353
+ }
354
+ @Override
355
+ public IRubyObject reject_bang(final ThreadContext context, final Block block) {
356
+ fillEntireHash();
357
+ return super.reject_bang(context, block);
358
+ }
359
+ @Override
360
+ public RubyHash rb_clear() {
361
+ fillEntireHash();
362
+ return super.rb_clear();
363
+ }
364
+ @Override
365
+ public RubyHash invert(final ThreadContext context) {
366
+ fillEntireHash();
367
+ return super.invert(context);
368
+ }
369
+ @Override
370
+ public RubyHash merge_bang(final ThreadContext context, final IRubyObject other, final Block block) {
371
+ fillEntireHash();
372
+ return super.merge_bang(context, other, block);
373
+ }
374
+ @Override
375
+ public RubyHash merge_bang19(final ThreadContext context, final IRubyObject other, final Block block) {
376
+ fillEntireHash();
377
+ return super.merge_bang19(context, other, block);
378
+ }
379
+ @Override
380
+ public RubyHash merge(ThreadContext context, IRubyObject other, Block block) {
381
+ fillEntireHash();
382
+ return super.merge(context, other, block);
383
+ }
384
+ @Override
385
+ public RubyHash initialize_copy(ThreadContext context, IRubyObject other) {
386
+ fillEntireHash();
387
+ return super.initialize_copy(context, other);
388
+ }
389
+ @Override
390
+ public RubyHash initialize_copy19(ThreadContext context, IRubyObject other) {
391
+ fillEntireHash();
392
+ return super.initialize_copy19(context, other);
393
+ }
394
+ @Override
395
+ public RubyHash replace(final ThreadContext context, IRubyObject other) {
396
+ fillEntireHash();
397
+ return super.replace(context, other);
398
+ }
399
+ @Override
400
+ public RubyHash replace19(final ThreadContext context, IRubyObject other) {
401
+ fillEntireHash();
402
+ return super.replace19(context, other);
403
+ }
404
+ @Override
405
+ public RubyArray values_at(ThreadContext context, IRubyObject[] args) {
406
+ fillEntireHash();
407
+ return super.values_at(context, args);
408
+ }
409
+ @Override
410
+ public IRubyObject assoc(final ThreadContext context, final IRubyObject obj) {
411
+ fillEntireHash();
412
+ return super.assoc(context, obj);
413
+ }
414
+ @Override
415
+ public IRubyObject rassoc(final ThreadContext context, final IRubyObject obj) {
416
+ fillEntireHash();
417
+ return super.rassoc(context, obj);
418
+ }
419
+ @Override
420
+ public IRubyObject flatten(ThreadContext context) {
421
+ fillEntireHash();
422
+ return super.flatten(context);
423
+ }
424
+ @Override
425
+ public IRubyObject flatten(ThreadContext context, IRubyObject level) {
426
+ fillEntireHash();
427
+ return super.flatten(context, level);
428
+ }
429
+ @Override
430
+ public IRubyObject getCompareByIdentity(ThreadContext context) {
431
+ fillEntireHash();
432
+ return super.getCompareByIdentity(context);
433
+ }
434
+ @Override
435
+ public IRubyObject getCompareByIdentity_p(ThreadContext context) {
436
+ fillEntireHash();
437
+ return super.getCompareByIdentity_p(context);
438
+ }
439
+ @Override
440
+ public IRubyObject dup(ThreadContext context) {
441
+ fillEntireHash();
442
+ return super.dup(context);
443
+ }
444
+ @Override
445
+ public IRubyObject rbClone(ThreadContext context) {
446
+ fillEntireHash();
447
+ return super.rbClone(context);
448
+ }
449
+ }