Package io.apigee.trireme.core.modules

Source Code of io.apigee.trireme.core.modules.HTTPParser$ParserImpl

/**
* Copyright 2013 Apigee Corporation.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package io.apigee.trireme.core.modules;

import io.apigee.trireme.core.NodeRuntime;
import io.apigee.trireme.core.InternalNodeModule;
import io.apigee.trireme.core.Utils;
import io.apigee.trireme.core.internal.Charsets;
import io.apigee.trireme.core.internal.NodeOSException;
import io.apigee.trireme.net.HTTPParsingMachine;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.Function;
import org.mozilla.javascript.FunctionObject;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.ScriptableObject;
import org.mozilla.javascript.ScriptRuntime;
import org.mozilla.javascript.annotations.JSFunction;
import org.mozilla.javascript.annotations.JSGetter;
import org.mozilla.javascript.annotations.JSSetter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static io.apigee.trireme.core.ArgUtils.*;

import java.lang.reflect.InvocationTargetException;
import java.nio.ByteBuffer;
import java.util.List;
import java.util.Map;

/**
* This is the HTTP parser required by Node's regular "http" module. It won't be used when an HTTP
* adapter is present but it will be otherwise.
*/
public class HTTPParser
    implements InternalNodeModule
{
    protected static final Logger log = LoggerFactory.getLogger(HTTPParser.class);

    @Override
    public String getModuleName()
    {
        return "http_parser";
    }

    @Override
    public Scriptable registerExports(Context cx, Scriptable scope, NodeRuntime runner)
        throws InvocationTargetException, IllegalAccessException, InstantiationException
    {
        Scriptable exports = cx.newObject(scope);
        exports.setParentScope(null);
        exports.setPrototype(scope);

        ScriptableObject.defineClass(exports, ParserImpl.class);
        FunctionObject fn = new FunctionObject("HTTPParser", Utils.findMethod(ParserImpl.class, "newParser"),
                                               exports);
        fn.put("REQUEST", fn, ParserImpl.REQUEST);
        fn.put("RESPONSE", fn, ParserImpl.RESPONSE);
        exports.put("HTTPParser", exports, fn);
        return exports;
    }

    public static class ParserImpl
        extends ScriptableObject
    {
        public static final String CLASS_NAME = "_httpParserClass";

        private static final ByteBuffer EMPTY_BUF = ByteBuffer.allocate(0);

        public static final int REQUEST  = 1;
        public static final int RESPONSE = 2;

        private HTTPParsingMachine parser;
        private boolean sentPartialHeaders;
        private boolean sentCompleteHeaders;

        private Function onHeaders;
        private Function onHeadersComplete;
        private Function onBody;
        private Function onMessageComplete;

        @Override
        public String getClassName()
        {
            return CLASS_NAME;
        }

        private void init(int type)
        {
            parser = new HTTPParsingMachine(
                (type == REQUEST) ? HTTPParsingMachine.ParsingMode.REQUEST : HTTPParsingMachine.ParsingMode.RESPONSE);
        }

        public static Object newParser(Context cx, Scriptable thisObj, Object[] args, Function fn)
        {
            int typeArg = intArg(args, 0);
            ParserImpl parser = (ParserImpl)cx.newObject(thisObj, CLASS_NAME);
            parser.init(typeArg);
            return parser;
        }

        @JSGetter("onHeaders")
        @SuppressWarnings("unused")
        public Function getOnHeaders()
        {
            return onHeaders;
        }

        @JSSetter("onHeaders")
        @SuppressWarnings("unused")
        public void setOnHeaders(Function onHeaders)
        {
            this.onHeaders = onHeaders;
        }

        @JSGetter("onHeadersComplete")
        @SuppressWarnings("unused")
        public Function getOnHeadersComplete()
        {
            return onHeadersComplete;
        }

        @JSSetter("onHeadersComplete")
        @SuppressWarnings("unused")
        public void setOnHeadersComplete(Function onHeadersComplete)
        {
            this.onHeadersComplete = onHeadersComplete;
        }

        @JSGetter("onBody")
        @SuppressWarnings("unused")
        public Function getOnBody()
        {
            return onBody;
        }

        @JSSetter("onBody")
        @SuppressWarnings("unused")
        public void setOnBody(Function onBody)
        {
            this.onBody = onBody;
        }

        @JSGetter("onMessageComplete")
        @SuppressWarnings("unused")
        public Function getOnMessageComplete()
        {
            return onMessageComplete;
        }

        @JSSetter("onMessageComplete")
        @SuppressWarnings("unused")
        public void setOnMessageComplete(Function onMessageComplete)
        {
            this.onMessageComplete = onMessageComplete;
        }

        @JSFunction
        @SuppressWarnings("unused")
        public void reinitialize(int type)
        {
            log.debug("HTTP parser: init");
            init(type);
        }

        @JSFunction
        @SuppressWarnings("unused")
        public void pause()
        {
            // I don't think that we have anything to pause in this implementation. The implementation
            // in node's "deps" seems to only be an error check as well.
        }

        @JSFunction
        @SuppressWarnings("unused")
        public void resume()
        {
        }

        @JSFunction
        @SuppressWarnings("unused")
        public static void finish(Context cx, Scriptable thisObj, Object[] args, Function ctorObj)
        {
            log.debug("HTTP parser finished with input");
            ((ParserImpl)thisObj).finish(cx);
        }

        private Object finish(Context cx)
        {
            try {
                execute(cx, EMPTY_BUF);
                return Context.getUndefinedValue();
            } catch (NodeOSException ne) {
                return Utils.makeErrorObject(cx, this, "Parse Error", ne.getCode());
            }
        }

        @JSFunction
        @SuppressWarnings("unused")
        public static Object execute(Context cx, Scriptable thisObj, Object[] args, Function ctorObj)
        {
            Buffer.BufferImpl bufObj = objArg(args, 0, Buffer.BufferImpl.class, true);
            int offset = intArg(args, 1);
            int length = intArg(args, 2);
            ParserImpl p = (ParserImpl)thisObj;

            ByteBuffer bBuf = bufObj.getBuffer();
            bBuf.position(bBuf.position() + offset);
            bBuf.limit(bBuf.position() + length);

            try {
                return p.execute(cx, bBuf);
            } catch (NodeOSException noe) {
                return Utils.makeErrorObject(cx, thisObj, "Parse Error", noe.getCode());
            }
        }

        private int execute(Context cx, ByteBuffer bBuf)
        {
            HTTPParsingMachine.Result result;
            boolean hadSomething;
            boolean wasComplete;
            int startPos = bBuf.position();

            do {
                if (log.isDebugEnabled()) {
                    log.debug("Parser.execute: buf = {}", bBuf);
                    if (log.isTraceEnabled()) {
                        log.trace("buffer: " + Utils.bufferToString(bBuf, Charsets.DEFAULT));
                    }
                }
                hadSomething = false;
                wasComplete = false;
                result = parser.parse(bBuf);
                if (result.isError()) {
                    if (log.isDebugEnabled()) {
                        log.debug("HTTP parser error");
                    }
                    throw new NodeOSException("HPE_INVALID_CONSTANT");
                }
                if (!sentCompleteHeaders) {
                    if (result.isHeadersComplete() || result.isComplete()) {
                        sentCompleteHeaders = true;
                        hadSomething = true;
                        log.debug("Sending complete HTTP headers");
                        if (result.hasHeaders() && sentPartialHeaders) {
                            callOnHeaders(cx, result);
                        }
                        if (callOnHeadersComplete(cx, result)) {
                            // The JS code returns this when the request was a HEAD
                            parser.setIgnoreBody(true);
                        }
                    } else if (result.hasHeaders()) {
                        hadSomething = true;
                        log.debug("Sending partial HTTP headers");
                        sentPartialHeaders = true;
                        callOnHeaders(cx, result);
                    }
                }
                if (result.hasTrailers()) {
                    hadSomething = true;
                    if (log.isDebugEnabled()) {
                        log.debug("Sending HTTP trailers");
                    }
                    callOnTrailers(cx, result);
                }
                if (result.hasBody()) {
                    hadSomething = true;
                    if (log.isDebugEnabled()) {
                        log.debug("Sending HTTP body {}", result.getBody());
                    }
                    callOnBody(cx, result);
                }
                if (result.isComplete()) {
                    log.debug("Sending HTTP request complete");
                    hadSomething = true;
                    wasComplete = true;
                    callOnComplete(cx);

                    // Reset so that the loop starts where we picked up
                    sentPartialHeaders = false;
                    sentCompleteHeaders = false;
                    parser.reset();
                }
                log.debug("hadSomething = {} result.isComplete = {} remaining = {} ret = {}",
                          hadSomething, wasComplete, bBuf.remaining(), bBuf.position() - startPos);
                // We're done consuming input, but re-loop in case the buffer has more.
                // however we need special handling for CONNECT requests so we don't consume the body.
                // TODO maybe we loop at the top level...
            } while (hadSomething && bBuf.hasRemaining() && !result.isConnectRequest());
            return bBuf.position() - startPos;
        }

        private boolean callOnHeadersComplete(Context cx, HTTPParsingMachine.Result result)
        {
            if (onHeadersComplete == null) {
                return false;
            }
            Scriptable info = cx.newObject(this);
            if (!sentPartialHeaders) {
                Scriptable headers = buildHeaders(cx, result);
                info.put("headers", info, headers);
            }
            info.put("url", info, result.getUri());
            info.put("versionMajor", info, result.getMajor());
            info.put("versionMinor", info, result.getMinor());
            info.put("method", info, result.getMethod());
            info.put("statusCode", info, result.getStatusCode());
            // HTTP code requires this hint to handle upgrade and connect requests
            info.put("upgrade", info, result.isUpgradeRequested() || ("connect".equalsIgnoreCase(result.getMethod())));
            info.put("shouldKeepAlive", info, result.shouldKeepAlive());
            Object ret = onHeadersComplete.call(cx, onHeadersComplete, this, new Object[] { info });
            if ((ret == null) || (!(ret instanceof Boolean))) {
                return false;
            }
            return (Boolean)ret;
        }

        private void callOnHeaders(Context cx, HTTPParsingMachine.Result result)
        {
            if (onHeaders == null) {
                return;
            }
            Scriptable headers = buildHeaders(cx, result);
            onHeaders.call(cx, onHeaders, this, new Object[] { headers, result.getUri() });
        }

        private void callOnTrailers(Context cx, HTTPParsingMachine.Result result)
        {
            if (onHeaders == null) {
                return;
            }
            Scriptable trailers = buildTrailers(cx, result);
            onHeaders.call(cx, onHeaders, this, new Object[] { trailers, result.getUri() });
        }

        private void callOnBody(Context cx, HTTPParsingMachine.Result result)
        {
            if (onBody == null) {
                return;
            }
            Buffer.BufferImpl buf = Buffer.BufferImpl.newBuffer(cx, this, result.getBody(), false);
            onBody.call(cx, onBody, this,
                        new Object[] { buf, 0, buf.getLength() });
        }

        private void callOnComplete(Context cx)
        {
            if (onMessageComplete == null) {
                return;
            }
            onMessageComplete.call(cx, onMessageComplete, this, ScriptRuntime.emptyArgs);
        }

        private Scriptable buildHeaders(Context cx, HTTPParsingMachine.Result result)
        {
            return buildMap(cx, result.getHeaders());
        }

        private Scriptable buildTrailers(Context cx, HTTPParsingMachine.Result result)
        {
            return buildMap(cx, result.getTrailers());
        }

        private Scriptable buildMap(Context cx, List<Map.Entry<String, String>> l)
        {
            if (l == null) {
                return cx.newArray(this, 0);
            }
            Object[] headers = new Object[l.size() * 2];
            int i = 0;
            for (Map.Entry<String, String> hdr : l) {
                headers[i++] = hdr.getKey();
                headers[i++] = hdr.getValue();
            }
            return cx.newArray(this, headers);
        }
    }
}
TOP

Related Classes of io.apigee.trireme.core.modules.HTTPParser$ParserImpl

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.