package org.jruby.jubilee; import io.netty.handler.codec.http.HttpHeaders; import io.vertx.core.MultiMap; import io.vertx.core.http.HttpServerRequest; import io.vertx.core.http.HttpVersion; import io.vertx.core.net.SocketAddress; import org.jruby.*; import org.jruby.jubilee.utils.RubyHelper; import org.jruby.runtime.*; import org.jruby.runtime.builtin.IRubyObject; import java.io.IOException; 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)); String httpVersion = "1.1"; if (request.version() == HttpVersion.HTTP_1_0) { httpVersion = "1.0"; } env.lazyPut(RACK_KEY.REQUEST_METHOD, request.method().toString(), 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, httpVersion, 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 (disabled) 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) { SocketAddress sourceAddress = request.remoteAddress(); if (sourceAddress == null) { return ""; } return sourceAddress.host(); } 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; }