org.jboss.remoting.transport.http.HTTPServerInvoker.java Source code

Java tutorial

Introduction

Here is the source code for org.jboss.remoting.transport.http.HTTPServerInvoker.java

Source

/*
* JBoss, Home of Professional Open Source
* Copyright 2005, JBoss Inc., and individual contributors as indicated
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/

package org.jboss.remoting.transport.http;

import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.HeaderElement;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.HttpParser;
import org.apache.commons.httpclient.NameValuePair;
import org.jboss.remoting.InvocationRequest;
import org.jboss.remoting.InvokerLocator;
import org.jboss.remoting.Version;
import org.jboss.remoting.marshal.Marshaller;
import org.jboss.remoting.marshal.UnMarshaller;
import org.jboss.remoting.transport.web.WebServerInvoker;
import org.jboss.remoting.transport.web.WebUtil;
import org.jboss.util.threadpool.BasicThreadPool;
import org.jboss.util.threadpool.BlockingMode;
import org.jboss.util.threadpool.ThreadPool;
import org.jboss.util.threadpool.ThreadPoolMBean;
import org.jboss.logging.Logger;

import javax.management.MBeanServer;
import javax.management.MBeanServerInvocationHandler;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import javax.net.ServerSocketFactory;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FilterInputStream;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

/**
 * This is the stand alone http server invoker which acts basically as a web server.
 *
 * Server invoker implementation based on http protocol.  Is basically a stand alone http server whose request are
 * forwared to the invocation handler and responses from invocation handler are sent back to caller as http response.
 * @deprecated This class has been replaced by org.jboss.remoting.transport.coyote.CoyoteInvoker, which is used
 * as the default server invoker for the http and https transport on the server side.  This class will be removed
 * from remoting distribution in a future release.
 * @author <a href="mailto:telrod@e2technologies.net">Tom Elrod</a>
 */
public class HTTPServerInvoker extends WebServerInvoker implements Runnable {
    private static final Logger log = Logger.getLogger(HTTPServerInvoker.class);

    public static final String MAX_NUM_HTTP_THREADS_KEY = "maxNumThreadsHTTP";
    public static final String HTTP_THREAD_POOL_CLASS_KEY = "HTTPThreadPool";
    public static final String HTTP_KEEP_ALIVE_TIMEOUT_KEY = "keepAliveTimeout";

    private String httpThreadPoolClass = null;

    private static int BACKLOG_DEFAULT = 1000;
    private static int MAX_POOL_SIZE_DEFAULT = 100;

    private ServerSocket serverSocket = null;

    private boolean running = false;

    private ThreadPool httpThreadPool;
    private int maxPoolSize = MAX_POOL_SIZE_DEFAULT;

    protected int backlog = BACKLOG_DEFAULT;

    protected int keepAliveTimeout = 15000; // default to 15 (same as apache web server)

    private boolean newServerSocketFactory = false;

    // list of content types
    public static String HTML = "text/html";
    public static String PLAIN = "text/plain";
    public static String SOAP = "application/soap+xml";

    public HTTPServerInvoker(InvokerLocator locator) {
        super(locator);
    }

    public HTTPServerInvoker(InvokerLocator locator, Map configuration) {
        super(locator, configuration);
    }

    public int getKeepAliveTimeout() {
        return keepAliveTimeout;
    }

    public void setKeepAliveTimeout(int keepAliveTimeout) {
        this.keepAliveTimeout = keepAliveTimeout;
    }

    public void setNewServerSocketFactory(ServerSocketFactory serverSocketFactory) {
        newServerSocketFactory = true;
        setServerSocketFactory(serverSocketFactory);
    }

    private void refreshServerSocket() throws IOException {
        newServerSocketFactory = false;
        serverSocket.close();
        serverSocket = null;
        InetAddress bindAddress = InetAddress.getByName(getServerBindAddress());
        serverSocket = createServerSocket(getServerBindPort(), backlog, bindAddress);
    }

    protected void setup() throws Exception {
        super.setup();

        Map config = getConfiguration();
        String maxNumOfThreads = (String) config.get(MAX_NUM_HTTP_THREADS_KEY);
        if (maxNumOfThreads != null && maxNumOfThreads.length() > 0) {
            try {
                maxPoolSize = Integer.parseInt(maxNumOfThreads);
            } catch (NumberFormatException e) {
                log.error("Can not convert max number of threads value (" + maxNumOfThreads + ") into a number.");
            }
        }
        httpThreadPoolClass = (String) config.get(HTTP_THREAD_POOL_CLASS_KEY);

        String keepAliveTimeoutValue = (String) config.get(HTTP_KEEP_ALIVE_TIMEOUT_KEY);
        if (keepAliveTimeoutValue != null && keepAliveTimeoutValue.length() > 0) {
            try {
                keepAliveTimeout = Integer.parseInt(keepAliveTimeoutValue);
            } catch (NumberFormatException e) {
                log.error(
                        "Can not convert keep alive timeout value (" + keepAliveTimeoutValue + ") into a number.");
            }
        }

    }

    public void setMaxNumberOfHTTPThreads(int numOfThreads) {
        this.maxPoolSize = numOfThreads;
    }

    public int getMaxNumberOfHTTPThreads() {
        return this.maxPoolSize;
    }

    public ThreadPool getHTTPThreadPool() {
        if (httpThreadPool == null) {
            // if no thread pool class set, then use default BasicThreadPool
            if (httpThreadPoolClass == null || httpThreadPoolClass.length() == 0) {
                BasicThreadPool basicthreadpool = new BasicThreadPool("JBossRemoting - HTTP Server Invoker");
                basicthreadpool.setBlockingMode(BlockingMode.RUN);
                basicthreadpool.setMaximumPoolSize(maxPoolSize);
                httpThreadPool = basicthreadpool;
            } else {
                //first check to see if this is an ObjectName
                boolean isObjName = false;
                try {
                    ObjectName objName = new ObjectName(httpThreadPoolClass);
                    httpThreadPool = createThreadPoolProxy(objName);
                    isObjName = true;
                } catch (MalformedObjectNameException e) {
                    log.debug("Thread pool class supplied is not an object name.");
                }

                if (!isObjName) {
                    try {
                        httpThreadPool = (ThreadPool) Class.forName(httpThreadPoolClass, false, getClassLoader())
                                .newInstance();
                    } catch (Exception e) {
                        throw new RuntimeException(
                                "Error loading instance of ThreadPool based on class name: " + httpThreadPoolClass);
                    }
                }
            }
        }
        return httpThreadPool;
    }

    private ThreadPool createThreadPoolProxy(ObjectName objName) {
        ThreadPool pool;
        MBeanServer server = getMBeanServer();
        if (server != null) {
            ThreadPoolMBean poolMBean = (ThreadPoolMBean) MBeanServerInvocationHandler.newProxyInstance(server,
                    objName, ThreadPoolMBean.class, false);
            pool = poolMBean.getInstance();
        } else {
            throw new RuntimeException(
                    "Can not register MBean ThreadPool as the ServerInvoker has not been registered with a MBeanServer.");
        }
        return pool;
    }

    public void setHTTPThreadPool(ThreadPool pool) {
        this.httpThreadPool = pool;
    }

    public void start() throws IOException {
        if (!running) {
            try {
                ThreadPool httpThreadPool = getHTTPThreadPool();
                InetAddress bindAddress = InetAddress.getByName(getServerBindAddress());
                serverSocket = createServerSocket(getServerBindPort(), backlog, bindAddress);

                // prime the pool by starting up max
                for (int t = 0; t < maxPoolSize; t++) {
                    httpThreadPool.run(this);
                }

                running = true;

            } catch (IOException e) {
                log.error("Error starting ServerSocket.  Bind port: " + getServerBindPort() + ", bind address: "
                        + getServerBindAddress());
                throw e;
            }
        }
        super.start();
    }

    protected ServerSocket createServerSocket(int serverBindPort, int backlog, InetAddress bindAddress)
            throws IOException {
        return new ServerSocket(serverBindPort, backlog, bindAddress);
    }

    public void run() {
        try {
            if (newServerSocketFactory) {
                log.debug("got notice about new ServerSocketFactory");
                try {
                    log.debug("refreshing server socket");
                    refreshServerSocket();
                } catch (IOException e) {
                    log.debug("could not refresh server socket");
                    log.debug("message is: " + e.getMessage());
                }
                log.debug("server socket refreshed");

            }
            Socket socket = serverSocket.accept();
            BufferedInputStream dataInput = null;
            BufferedOutputStream dataOutput = null;

            if (socket != null) {
                // tell the pool to start another thread to listen for more socket requests.
                httpThreadPool.run(this);

                try {

                    boolean keepAlive = true;

                    // start processing incoming request from client.  will try to keep current connection alive
                    socket.setKeepAlive(true);
                    socket.setSoTimeout(keepAliveTimeout);

                    DataInputStream realdataInput = new DataInputStream(socket.getInputStream());
                    DataOutputStream realdataOutput = new DataOutputStream(socket.getOutputStream());

                    while (keepAlive) {
                        dataOutput = new BufferedOutputStream(realdataOutput, 512);
                        dataInput = new BufferedInputStream(realdataInput, 512);
                        keepAlive = processRequest(dataInput, dataOutput);
                    }

                } catch (Throwable thr) {
                    if (running) {
                        log.error("Error processing incoming request.", thr);
                    }
                } finally {
                    if (dataInput != null) {
                        try {
                            dataInput.close();
                        } catch (Exception e) {
                            log.warn("Error closing resource.", e);
                        }
                    }
                    if (dataOutput != null) {
                        try {
                            dataOutput.close();
                        } catch (Exception e) {
                            log.warn("Error closing resource.", e);
                        }
                    }
                    try {
                        socket.close();
                    } catch (Exception e) {
                        log.warn("Error closing resource.", e);
                    }
                }
            }
        } catch (Throwable thr) {
            if (running) {
                log.error("Error processing incoming request.", thr);
            }
        }
    }

    public void stop() {
        if (running) {
            running = false;

            maxPoolSize = 0; // so ServerThreads don't reinsert themselves

            try {
                httpThreadPool.stop(false);
                httpThreadPool.waitForTasks(2000);
            } catch (InterruptedException e) {
                log.error(e.getMessage(), e);
            }

            try {
                if (serverSocket != null && !serverSocket.isClosed()) {
                    serverSocket.close();
                }
                serverSocket = null;
            } catch (Exception e) {
            }
        }
        super.stop();

        log.debug("HTTPServerInvoker stopped.");
    }

    private boolean processRequest(FilterInputStream dataInput, FilterOutputStream dataOutput) {
        boolean keepAlive = true;
        try {
            Object response = null;
            boolean isError = false;
            String requestContentType = null;
            String methodType = null;
            String path = null;
            String httpVersion = null;

            InvocationRequest request = null;

            try {

                // Need to parse the header to find Content-Type

                /**
                 * Read the first line, as this will be the POST or GET, path, and HTTP version.
                 * Then next comes the headers.  (key value seperated by a ': '
                 * Then followed by an empty \r\n, which will be followed by the payload
                 */
                ByteArrayOutputStream buffer = new ByteArrayOutputStream();
                int ch;
                while ((ch = dataInput.read()) >= 0) {
                    buffer.write(ch);
                    if (ch == '\n') {
                        break;
                    }
                }

                byte[] firstLineRaw = buffer.toByteArray();
                buffer.close();
                // now need to backup and make sure the character before the \n was a \r
                if (firstLineRaw[firstLineRaw.length - 2] == '\r') {
                    //Got our first line, now to set the variables
                    String firstLine = new String(firstLineRaw).trim();
                    int startIndex = 0;
                    int endIndex = firstLine.indexOf(' ');
                    methodType = firstLine.substring(startIndex, endIndex);
                    startIndex = endIndex + 1;
                    endIndex = firstLine.indexOf(' ', startIndex);
                    path = firstLine.substring(startIndex, endIndex);
                    startIndex = endIndex + 1;
                    httpVersion = firstLine.substring(startIndex);
                } else {
                    log.error("Error processing first line.  Should have ended in \r\n, but did not");
                    throw new RuntimeException(
                            "Error processing HTTP request type.  First line of request is invalid.");
                }

                Map metadata = new HashMap();
                Header[] headers = HttpParser.parseHeaders(dataInput);
                for (int x = 0; x < headers.length; x++) {
                    String headerName = headers[x].getName();
                    String headerValue = headers[x].getValue();
                    metadata.put(headerName, headerValue);
                    // doing this instead of getting from map since ignores case
                    if ("Content-Type".equalsIgnoreCase(headerName)) {
                        requestContentType = headers[x].getValue();
                    }
                }

                metadata.put(HTTPMetadataConstants.METHODTYPE, methodType);
                metadata.put(HTTPMetadataConstants.PATH, path);
                metadata.put(HTTPMetadataConstants.HTTPVERSION, httpVersion);

                // checks to see if is Connection: close, which will deactivate keep alive.
                keepAlive = checkForConnecctionClose(headers);

                if (methodType.equals("OPTIONS")) {
                    request = createNewInvocationRequest(metadata, null);
                    response = invoke(request);

                    Map responseMap = request.getReturnPayload();

                    dataOutput.write("HTTP/1.1 ".getBytes());
                    String status = "200 OK";
                    dataOutput.write(status.getBytes());
                    String server = "\r\n" + "Server: JBoss Remoting HTTP Server/" + Version.VERSION;
                    dataOutput.write(server.getBytes());
                    String date = "\r\n" + "Date: " + new Date();
                    dataOutput.write(date.getBytes());
                    String contentLength = "\r\n" + "Content-Length: 0";
                    dataOutput.write(contentLength.getBytes());

                    if (responseMap != null) {
                        Set entries = responseMap.entrySet();
                        Iterator itr = entries.iterator();
                        while (itr.hasNext()) {
                            Map.Entry entry = (Map.Entry) itr.next();
                            String entryString = "\r\n" + entry.getKey() + ": " + entry.getValue();
                            dataOutput.write(entryString.getBytes());
                        }
                    }

                    String close = "\r\n" + "Connection: close";
                    dataOutput.write(close.getBytes());

                    // content seperator
                    dataOutput.write(("\r\n" + "\r\n").getBytes());

                    dataOutput.flush();

                    //nothing more to do since do not need to call handler for this one
                    return keepAlive;

                } else if (methodType.equals("GET") || methodType.equals("HEAD")) {
                    request = createNewInvocationRequest(metadata, null);
                } else // must be POST or PUT
                {
                    UnMarshaller unmarshaller = getUnMarshaller();
                    Object obj = unmarshaller.read(dataInput, metadata);

                    if (obj instanceof InvocationRequest) {
                        request = (InvocationRequest) obj;
                    } else {
                        if (WebUtil.isBinary(requestContentType)) {
                            request = getInvocationRequest(metadata, obj);
                        } else {
                            request = createNewInvocationRequest(metadata, obj);
                        }
                    }
                }

                try {
                    // call transport on the subclass, get the result to handback
                    response = invoke(request);
                } catch (Throwable ex) {
                    log.debug("Error thrown calling invoke on server invoker.", ex);
                    response = ex;
                    isError = true;
                }
            } catch (Throwable thr) {
                log.debug("Error thrown processing request.  Probably error with processing headers.", thr);
                if (thr instanceof SocketException) {
                    log.error("Error processing on socket.", thr);
                    keepAlive = false;
                    return keepAlive;
                } else if (thr instanceof Exception) {
                    response = (Exception) thr;
                } else {
                    response = new Exception(thr);
                }
                isError = true;
            }

            if (dataOutput != null) {

                Map responseMap = null;

                if (request != null) {
                    responseMap = request.getReturnPayload();
                }

                if (response == null) {
                    dataOutput.write("HTTP/1.1 ".getBytes());
                    String status = "204 No Content";
                    if (responseMap != null) {
                        String handlerStatus = (String) responseMap.get(HTTPMetadataConstants.RESPONSE_CODE);
                        if (handlerStatus != null) {
                            status = handlerStatus;
                        }
                    }
                    dataOutput.write(status.getBytes());
                    String contentType = "\r\n" + "Content-Type" + ": " + "text/html";
                    dataOutput.write(contentType.getBytes());
                    String contentLength = "\r\n" + "Content-Length" + ": " + 0;
                    dataOutput.write(contentLength.getBytes());
                    if (responseMap != null) {
                        Set entries = responseMap.entrySet();
                        Iterator itr = entries.iterator();
                        while (itr.hasNext()) {
                            Map.Entry entry = (Map.Entry) itr.next();
                            String entryString = "\r\n" + entry.getKey() + ": " + entry.getValue();
                            dataOutput.write(entryString.getBytes());
                        }
                    }

                    dataOutput.write(("\r\n" + "\r\n").getBytes());
                    //dataOutput.write("NULL return".getBytes());
                    dataOutput.flush();
                } else {
                    dataOutput.write("HTTP/1.1 ".getBytes());
                    String status = null;
                    if (isError) {
                        status = "500 JBoss Remoting: Error occurred within target application.";
                    } else {
                        status = "200 OK";
                        if (responseMap != null) {
                            String handlerStatus = (String) responseMap.get(HTTPMetadataConstants.RESPONSE_CODE);
                            if (handlerStatus != null) {
                                status = handlerStatus;
                            }
                        }
                    }

                    dataOutput.write(status.getBytes());

                    // write return headers
                    String contentType = "\r\n" + "Content-Type" + ": " + requestContentType;
                    dataOutput.write(contentType.getBytes());
                    int iContentLength = getContentLength(response);
                    String contentLength = "\r\n" + "Content-Length" + ": " + iContentLength;
                    dataOutput.write(contentLength.getBytes());

                    if (responseMap != null) {
                        Set entries = responseMap.entrySet();
                        Iterator itr = entries.iterator();
                        while (itr.hasNext()) {
                            Map.Entry entry = (Map.Entry) itr.next();
                            String entryString = "\r\n" + entry.getKey() + ": " + entry.getValue();
                            dataOutput.write(entryString.getBytes());
                        }
                    }

                    // content seperator
                    dataOutput.write(("\r\n" + "\r\n").getBytes());

                    if (methodType != null && !methodType.equals("HEAD")) {
                        // write response
                        Marshaller marshaller = getMarshaller();
                        marshaller.write(response, dataOutput);
                    }
                }
            } else {
                if (isError) {
                    log.warn(
                            "Can not send error response due to output stream being null (due to previous error).");
                } else {
                    log.error(
                            "Can not send response due to output stream being null (even though there was not a previous error encountered).");
                }
            }
        } catch (Exception e) {
            log.error("Error processing client request.", e);
            keepAlive = false;
        }

        return keepAlive;
    }

    private boolean checkForConnecctionClose(Header[] headers) {
        boolean keepAlive = true;

        if (headers != null) {
            for (int x = 0; x < headers.length; x++) {
                String name = headers[x].getName();
                if ("Connection".equals(name)) {
                    String value = headers[x].getValue();
                    if (value != null) {
                        if ("close".equalsIgnoreCase(value)) {
                            keepAlive = false;
                            break;
                        }
                    } else {
                        try {
                            HeaderElement[] hdrElements = headers[x].getValues();
                            if (hdrElements != null) {
                                for (int i = 0; i < hdrElements.length; i++) {
                                    NameValuePair pair = hdrElements[i].getParameterByName("Connection");
                                    if ("close".equalsIgnoreCase(pair.getValue())) {
                                        keepAlive = false;
                                        break;
                                    }
                                }
                            }
                        } catch (HttpException e) {
                            log.error(e.getMessage(), e);
                        }
                    }
                }
            }
        }

        return keepAlive;
    }
}