package org.jruby.jubilee; import io.netty.handler.codec.http.HttpHeaders; import org.jruby.*; import org.jruby.jubilee.utils.RubyHelper; import org.jruby.runtime.*; import org.jruby.runtime.builtin.IRubyObject; import org.vertx.java.core.MultiMap; import org.vertx.java.core.http.HttpServerRequest; import org.vertx.java.core.http.HttpVersion; import org.vertx.java.core.net.NetSocket; import java.io.IOException; import java.net.InetSocketAddress; import java.util.HashMap; import java.util.Map; public class RackEnvironment { // When adding a key to the enum be sure to add its RubyString equivalent // to populateRackKeyMap below static enum RACK_KEY { RACK_INPUT, RACK_ERRORS, REQUEST_METHOD, SCRIPT_NAME, PATH_INFO, QUERY_STRING, SERVER_NAME, SERVER_PORT, CONTENT_TYPE, REQUEST_URI, REMOTE_ADDR, URL_SCHEME, VERSION, MULTITHREAD, MULTIPROCESS, RUN_ONCE, CONTENT_LENGTH, HTTPS, HTTP_VERSION, HIJACK_P, HIJACK//, HIJACK_IO } static final int NUM_RACK_KEYS = RACK_KEY.values().length; public RackEnvironment(final Ruby runtime) throws IOException { this.runtime = runtime; this.netSocketClass = (RubyClass) runtime.getClassFromPath("Jubilee::NetSocket"); rackVersion = RubyArray.newArray(runtime, RubyFixnum.one(runtime), RubyFixnum.four(runtime)); errors = new RubyIO(runtime, runtime.getErr()); errors.setAutoclose(false); populateRackKeyMap(); } private void populateRackKeyMap() { putRack("rack.input", RACK_KEY.RACK_INPUT); putRack("rack.errors", RACK_KEY.RACK_ERRORS); putRack("REQUEST_METHOD", RACK_KEY.REQUEST_METHOD); putRack("SCRIPT_NAME", RACK_KEY.SCRIPT_NAME); putRack("PATH_INFO", RACK_KEY.PATH_INFO); putRack("QUERY_STRING", RACK_KEY.QUERY_STRING); putRack("SERVER_NAME", RACK_KEY.SERVER_NAME); putRack("SERVER_PORT", RACK_KEY.SERVER_PORT); putRack("HTTP_VERSION", RACK_KEY.HTTP_VERSION); putRack("CONTENT_TYPE", RACK_KEY.CONTENT_TYPE); putRack("REQUEST_URI", RACK_KEY.REQUEST_URI); putRack("REMOTE_ADDR", RACK_KEY.REMOTE_ADDR); putRack("rack.url_scheme", RACK_KEY.URL_SCHEME); putRack("rack.version", RACK_KEY.VERSION); putRack("rack.multithread", RACK_KEY.MULTITHREAD); putRack("rack.multiprocess", RACK_KEY.MULTIPROCESS); putRack("rack.run_once", RACK_KEY.RUN_ONCE); putRack("rack.hijack?", RACK_KEY.HIJACK_P); putRack("rack.hijack", RACK_KEY.HIJACK); // putRack("rack.hijack_io", RACK_KEY.HIJACK_IO); Don't have to be lazy, since once rack.hijack is called, the io // object has to be required by the caller. putRack("CONTENT_LENGTH", RACK_KEY.CONTENT_LENGTH); putRack("HTTPS", RACK_KEY.HTTPS); } private void putRack(String key, RACK_KEY value) { rackKeyMap.put(RubyHelper.toUsAsciiRubyString(runtime, key), value); } public RubyHash getEnv(final HttpServerRequest request, final RackInput input, final boolean isSSL) throws IOException { MultiMap headers = request.headers(); final RackEnvironmentHash env = new RackEnvironmentHash(runtime, headers, rackKeyMap); env.lazyPut(RACK_KEY.RACK_INPUT, input, false); env.lazyPut(RACK_KEY.RACK_ERRORS, errors, false); String pathInfo = request.path(); String scriptName = ""; String[] hostInfo = getHostInfo(request.headers().get(Const.HOST)); env.lazyPut(RACK_KEY.REQUEST_METHOD, request.method(), true); env.lazyPut(RACK_KEY.SCRIPT_NAME, scriptName, false); env.lazyPut(RACK_KEY.PATH_INFO, pathInfo, false); env.lazyPut(RACK_KEY.QUERY_STRING, orEmpty(request.query()), false); env.lazyPut(RACK_KEY.SERVER_NAME, hostInfo[0], false); env.lazyPut(RACK_KEY.SERVER_PORT, hostInfo[1], true); env.lazyPut(RACK_KEY.HTTP_VERSION, request.version() == HttpVersion.HTTP_1_1 ? Const.HTTP_11 : Const.HTTP_10, true); env.lazyPut(RACK_KEY.CONTENT_TYPE, headers.get(HttpHeaders.Names.CONTENT_TYPE), true); env.lazyPut(RACK_KEY.REQUEST_URI, request.uri(), false); env.lazyPut(RACK_KEY.REMOTE_ADDR, getRemoteAddr(request), true); env.lazyPut(RACK_KEY.URL_SCHEME, isSSL ? Const.HTTPS : Const.HTTP, true); env.lazyPut(RACK_KEY.VERSION, rackVersion, false); env.lazyPut(RACK_KEY.MULTITHREAD, runtime.getTrue(), false); env.lazyPut(RACK_KEY.MULTIPROCESS, runtime.getFalse(), false); env.lazyPut(RACK_KEY.RUN_ONCE, runtime.getFalse(), false); // Hijack handling env.lazyPut(RACK_KEY.HIJACK_P, runtime.getFalse(), false); // env.lazyPut(RACK_KEY.HIJACK, hijackProc(env, request), false); final int contentLength = getContentLength(headers); if (contentLength >= 0) { env.lazyPut(RACK_KEY.CONTENT_LENGTH, contentLength + "", true); } if (isSSL) { env.lazyPut(RACK_KEY.HTTPS, "on", true); } return env; } private IRubyObject hijackProc(final RackEnvironmentHash env, final HttpServerRequest req) { CompiledBlockCallback19 callback = new CompiledBlockCallback19() { @Override public IRubyObject call(ThreadContext context, IRubyObject self, IRubyObject[] args, Block block) { RubyNetSocket rubyNetSocket = new RubyNetSocket(context.runtime, netSocketClass, req.netSocket()); env.put("rack.hijack_io", rubyNetSocket); return rubyNetSocket; } @Override public String getFile() { return null; } @Override public int getLine() { return 0; } }; BlockBody body = CompiledBlockLight19.newCompiledBlockLight(Arity.NO_ARGUMENTS, runtime.getStaticScopeFactory().getDummyScope(), callback, false, 0, new String[]{}); Block b = new Block(body, runtime.newBinding().getBinding()); return RubyProc.newProc(runtime, b, Block.Type.LAMBDA); } public String[] getHostInfo(String host) { String[] hostInfo; if (host != null) { int colon = host.indexOf(":"); if (colon > 0) hostInfo = new String[]{host.substring(0, colon), host.substring(colon + 1)}; else hostInfo = new String[]{host, Const.PORT_80}; } else { hostInfo = new String[]{Const.LOCALHOST, Const.PORT_80}; } return hostInfo; } private static String getRemoteAddr(final HttpServerRequest request) { InetSocketAddress sourceAddress = request.remoteAddress(); if (sourceAddress == null) { return ""; } return sourceAddress.getHostString(); } private static int getContentLength(final MultiMap headers) { final String contentLengthStr = headers.get(HttpHeaders.Names.CONTENT_LENGTH); if (contentLengthStr == null || contentLengthStr.isEmpty()) { return -1; } return Integer.parseInt(contentLengthStr); } private String orEmpty(String val) { return val == null ? "" : val; } private final Ruby runtime; private final RubyArray rackVersion; private final RubyIO errors; private final Map rackKeyMap = new HashMap<>(); private final RubyClass netSocketClass; }