All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sun.grizzly.jruby.rack.AbstractRackApplication Maven / Gradle / Ivy

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License. You can obtain
 * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
 * or glassfish/bootstrap/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
 * Sun designates this particular file as subject to the "Classpath" exception
 * as provided by Sun in the GPL Version 2 section of the License file that
 * accompanied this code.  If applicable, add the following below the License
 * Header, with the fields enclosed by brackets [] replaced by your own
 * identifying information: "Portions Copyrighted [year]
 * [name of copyright owner]"
 *
 * Contributor(s):
 *
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */
package com.sun.grizzly.jruby.rack;

import com.sun.grizzly.jruby.RackGrizzlyAdapter;
import com.sun.grizzly.tcp.ActionCode;
import com.sun.grizzly.tcp.Request;
import com.sun.grizzly.tcp.http11.GrizzlyRequest;
import com.sun.grizzly.util.buf.ByteChunk;
import com.sun.grizzly.util.buf.MessageBytes;
import com.sun.grizzly.util.http.MimeHeaders;
import org.jruby.Ruby;
import org.jruby.RubyHash;
import org.jruby.RubyIO;
import org.jruby.RubyString;
import org.jruby.javasupport.JavaEmbedUtils;
import org.jruby.runtime.builtin.IRubyObject;

import java.io.IOException;
import java.nio.channels.Channels;
import java.util.Map;
import java.util.logging.Logger;

/**
 * @author Vivek Pandey
 */
public abstract class AbstractRackApplication implements RackApplication {
    private final IRubyObject application;
    public final Ruby runtime;
    private final Logger logger;
    private final RubyString rack_version;
    private final RubyString rack_multithread;
    private final RubyString rack_multiprocess;
    private final RubyString rack_run_once;
    private final RubyHash base;
    private final RubyString rack_input;
    private final RubyString rack_errors;
    private final RubyString rack_url_scheme;


    //Request methods
    private final RubyString req_method;
    private final RubyString script_name;
    private final RubyString request_uri;
    private final RubyString path_info;
    private final RubyString query_string;
    private final RubyString path_translated;
    private final RubyString server_name;
    private final RubyString remote_host;
    private final RubyString remote_addr;
    private final RubyString remote_user;
    private final RubyString server_port;
    private final RubyString content_type;
    private final RubyString content_length;
    private final RubyString server_software;

    protected final RackGrizzlyAdapter adapter;

    public AbstractRackApplication(IRubyObject application, RackGrizzlyAdapter adapter) {
        this.application = application;
        this.runtime = application.getRuntime();
        this.logger = adapter.config.getLogger();
        this.adapter = adapter;

        base = RubyHash.newHash(runtime);

        rack_version = runtime.newString("rack.version");
        rack_multithread = runtime.newString("rack.multithread");
        rack_multiprocess = runtime.newString("rack.multiprocess");
        rack_run_once = runtime.newString("rack.run_once");
        rack_input = runtime.newString("rack.input");
        rack_errors = runtime.newString("rack.errors");
        rack_url_scheme = runtime.newString("rack.url_scheme");

        base.put(rack_version, runtime.evalScriptlet("Rack::VERSION"));
        base.put(rack_multithread, runtime.getTrue());
        base.put(rack_multiprocess, runtime.getFalse());
        base.put(rack_run_once, runtime.getFalse());
        base.put(rack_errors, runtime.evalScriptlet("JRuby::Rack::GrizzlyLog.new"));

        req_method = runtime.newString("REQUEST_METHOD");
        script_name = runtime.newString("SCRIPT_NAME");
        request_uri = runtime.newString("REQUEST_URI");
        path_info = runtime.newString("PATH_INFO");
        query_string = runtime.newString("QUERY_STRING");
        path_translated = runtime.newString("PATH_TRANSLATED");
        server_name = runtime.newString("SERVER_NAME");
        remote_host = runtime.newString("REMOTE_HOST");
        remote_addr = runtime.newString("REMOTE_ADDR");
        remote_user = runtime.newString("REMOTE_USER");
        server_port = runtime.newString("SERVER_PORT");
        content_type = runtime.newString("CONTENT_TYPE");
        content_length = runtime.newString("CONTENT_LENGTH");
        server_software = runtime.newString("SERVER_SOTWARE");

        String serverSoftware = System.getProperty("server.software");
        if(serverSoftware == null){
            serverSoftware = "GlassFish v3";
        }

        base.put(server_software,  runtime.newString(serverSoftware));
    }

    public RackResponse call(final GrizzlyRequest grizzlyRequest) {
        Ruby runtime = application.getRuntime();
        RubyHash rackEnv = (RubyHash) base.dup();

        Request request = grizzlyRequest.getRequest();

        addReqInfo(grizzlyRequest, rackEnv);


        populateFromMimeHeaders(rackEnv, request.getMimeHeaders());
        populateFromMap(rackEnv, request.getAttributes());

        try {
            rackEnv.put(rack_input, RubyIO.newIO(runtime, Channels.newChannel(grizzlyRequest.getInputStream())));
        } catch (IOException ioe) {
            throw runtime.newIOErrorFromException(ioe);
        }
        rackEnv.put(rack_url_scheme, grizzlyRequest.getScheme());

        IRubyObject response = application.callMethod(runtime.getCurrentContext(),
                "call", rackEnv);
        Object obj = JavaEmbedUtils.rubyToJava(runtime, response, RackResponse.class);
        return (RackResponse) obj;
    }

    private void addReqInfo(GrizzlyRequest grequest, RubyHash hash){
        Request request = grequest.getRequest();

        RubyString reqUri;

        //skip context path
        if(adapter.config.contextRoot().length() > 1){
            byte[] bytes = request.requestURI().getByteChunk().getBuffer();
            byte[] contextpath = adapter.config.contextRoot().getBytes();

            boolean hasContextRoot = true;
            int offset=request.requestURI().getByteChunk().getOffset();

            for (int i = 0; i < contextpath.length; i++) {
                if (bytes[offset] != contextpath[i]) {
                    hasContextRoot = false;
                    break;
                }

                if (offset < bytes.length) {
                    offset++;
                }
            }
            if(hasContextRoot){
                int lenoffset = (offset > request.requestURI().getByteChunk().getOffset())?offset-request.requestURI().getByteChunk().getOffset():offset;
                reqUri = RubyString.newStringShared(
                runtime,
                bytes,
                offset,
                request.requestURI().getByteChunk().getLength() - lenoffset);
            }else{
                reqUri = newStringFromMessageBytes(request.requestURI());
            }
           hash.put(request_uri, newStringFromMessageBytes(request.requestURI()));
        }else{
            reqUri = newStringFromMessageBytes(request.requestURI());
        }


        hash.put(path_info, reqUri);
        hash.put(path_translated, reqUri);

        add(hash, req_method, request.method());
        add(hash, query_string, request.queryString());

         //grizzly reads these headers lazily, so need to send actions.
        request.action(ActionCode.ACTION_REQ_LOCAL_NAME_ATTRIBUTE, null);
        add(hash, server_name, request.serverName());
        request.action(ActionCode.ACTION_REQ_HOST_ATTRIBUTE, null);
        add(hash, remote_host, request.remoteHost());
        request.action(ActionCode.ACTION_REQ_HOST_ADDR_ATTRIBUTE, null);
        add(hash, remote_addr, request.remoteAddr());
        request.action(ActionCode.ACTION_REQ_LOCALPORT_ATTRIBUTE, null);
        hash.put(server_port, runtime.newString(Integer.toString(request.getLocalPort())));

        //There is no action for remote user from grizzly. Hopefully it will be available.
        add(hash, remote_user, request.getRemoteUser());

        hash.put(script_name, runtime.newString(""));

        if(request.getContentLength() > 0){
            hash.put(content_length, runtime.newString(Integer.toString(request.getContentLength())));
        }

        add(hash, content_type, request.contentType());
    }

    private void add(RubyHash hash, RubyString key, MessageBytes value){
        if(value == null || value.isNull())
            return;
        
        //For many mime headers, grizzly maynot have MEssageBytes created from byte[]. so lets get things moved to byte[]
        value.toBytes();

        hash.put(key, newStringFromMessageBytes(value));
    }

    private void populateFromMap(RubyHash env, Map source) {
        for (Object obj : source.entrySet()) {
            Map.Entry entry = (Map.Entry) obj;
            env.put(
                    newStringFromMessageBytes((MessageBytes) entry.getKey()),
                    newStringFromMessageBytes((MessageBytes) entry.getValue()));
        }
    }

    private void populateFromMimeHeaders(RubyHash env, MimeHeaders source) {
        for (int i = 0; i < source.size(); i++) {
            MessageBytes key = source.getName(i);

            //Rack spec disallows adding content-type and content-length with HTTP_ prefix
            //Need to find a way to ignore it.
            if(key.startsWithIgnoreCase("content-type", 0) ||
                    key.startsWithIgnoreCase("content-length", 0))
                continue;

            MessageBytes mb = MessageBytes.newInstance();

            byte[] httpKey = {'H', 'T', 'T', 'P','_'};
            try {
                mb.getByteChunk().append(httpKey, 0, httpKey.length);
                byte[] bytes = key.getByteChunk().getBuffer();
                for(int k=0; k < key.getByteChunk().getLength();k++){
                    bytes[key.getByteChunk().getOffset() + k] = (byte) Character.toUpperCase(bytes[key.getByteChunk().getOffset() + k]);
                    if(bytes[key.getByteChunk().getOffset() + k] == '-')
                        bytes[key.getByteChunk().getOffset() + k] = '_';
                }
                mb.getByteChunk().append(bytes, key.getByteChunk().getOffset(), key.getByteChunk().getLength());
            } catch (IOException e) {
                throw runtime.newIOErrorFromException(e);
            }
            MessageBytes value = source.getValue(i);
            env.put(
                    newStringFromMessageBytes(mb),
                    newStringFromMessageBytes(value));
        }
    }

    private RubyString newStringFromMessageBytes(MessageBytes messageBytes) {
        if (messageBytes == null) {
            //return RubyString.newEmptyString(runtime);
            // Can't return the empty string, since it has to actually be a nil
            return null;
        } else {
            ByteChunk chunk = messageBytes.getByteChunk();
            byte[] bytes = chunk.getBuffer();
            if (bytes == null) {
                return RubyString.newEmptyString(runtime);
            } else {
                return RubyString.newStringShared(
                        runtime,
                        bytes,
                        chunk.getStart(),
                        chunk.getLength());
            }
        }
    }

    public void destroy() {
        if(runtime != null)
            runtime.tearDown();
    }

    public boolean isMTSafe(){
        return adapter.config.isMTSafe();
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy