jubilee 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.rbenv-version +1 -0
- data/Gemfile +16 -0
- data/Gemfile.lock +54 -0
- data/Guardfile +24 -0
- data/README.md +61 -0
- data/Rakefile +94 -0
- data/VERSION +1 -0
- data/bin/jubilee +6 -0
- data/bin/jubilee_d +10 -0
- data/examples/jubilee/keystore.jks +0 -0
- data/examples/jubilee/server-keystore.jks +0 -0
- data/examples/ssl/ServerTest.java +19 -0
- data/examples/ssl/webroot/index.html +10 -0
- data/jars/netty-3.6.0.Beta1.jar +0 -0
- data/jars/vertx-core-1.3.0.final.jar +0 -0
- data/java/.idea/ant.xml +7 -0
- data/java/.idea/libraries/jruby.xml +9 -0
- data/java/.idea/libraries/netty_3_6_0_Beta1.xml +9 -0
- data/java/.idea/libraries/vertx_core_1_3_0_final.xml +9 -0
- data/java/src/jubilee/JubileeService.java +21 -0
- data/java/src/org/jruby/jubilee/Const.java +148 -0
- data/java/src/org/jruby/jubilee/RackApplication.java +78 -0
- data/java/src/org/jruby/jubilee/RackEnvironment.java +13 -0
- data/java/src/org/jruby/jubilee/RackErrors.java +44 -0
- data/java/src/org/jruby/jubilee/RackInput.java +62 -0
- data/java/src/org/jruby/jubilee/RackResponse.java +16 -0
- data/java/src/org/jruby/jubilee/Server.java +104 -0
- data/java/src/org/jruby/jubilee/deploy/Starter.java +26 -0
- data/java/src/org/jruby/jubilee/impl/DefaultRackEnvironment.java +98 -0
- data/java/src/org/jruby/jubilee/impl/NullIO.java +111 -0
- data/java/src/org/jruby/jubilee/impl/RubyIORackErrors.java +68 -0
- data/java/src/org/jruby/jubilee/impl/RubyIORackInput.java +164 -0
- data/lib/jubilee.rb +11 -0
- data/lib/jubilee/application.rb +13 -0
- data/lib/jubilee/cli.rb +74 -0
- data/lib/jubilee/configuration.rb +52 -0
- data/lib/jubilee/const.rb +39 -0
- data/lib/jubilee/jubilee.jar +0 -0
- data/lib/jubilee/response.rb +64 -0
- data/lib/jubilee/server.rb +16 -0
- data/lib/rack/handler/jubilee.rb +43 -0
- data/test/.rbenv-version +1 -0
- data/test/config/app.rb +5 -0
- data/test/jubilee/test_cli.rb +11 -0
- data/test/jubilee/test_config.rb +14 -0
- data/test/jubilee/test_persistent.rb +238 -0
- data/test/jubilee/test_rack_server.rb +116 -0
- data/test/jubilee/test_server.rb +68 -0
- data/test/sinatra_app/app.rb +31 -0
- data/test/sinatra_app/config.ru +6 -0
- data/test/sinatra_app/public/test.html +10 -0
- data/test/sinatra_app/unicorn.conf.rb +29 -0
- data/test/test_helper.rb +21 -0
- 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,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
|
+
}
|