org.jsnap.request.HttpRequest.java Source code

Java tutorial

Introduction

Here is the source code for org.jsnap.request.HttpRequest.java

Source

/************************************************************************
 * This file is part of jsnap.                                          *
 *                                                                      *
 * jsnap is free software: you can redistribute it and/or modify        *
 * it under the terms of the GNU General Public License as published by *
 * the Free Software Foundation, either version 3 of the License, or    *
 * (at your option) any later version.                                  *
 *                                                                      *
 * jsnap 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 General Public License for more details.                         *
 *                                                                      *
 * You should have received a copy of the GNU General Public License    *
 * along with jsnap.  If not, see <http://www.gnu.org/licenses/>.       *
 ************************************************************************/

package org.jsnap.request;

import java.io.IOException;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
import java.net.Socket;
import java.net.URLEncoder;

import org.apache.http.HttpClientConnection;
import org.apache.http.HttpEntity;
import org.apache.http.HttpException;
import org.apache.http.HttpHost;
import org.apache.http.HttpStatus;
import org.apache.http.Scheme;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.impl.DefaultHttpClientConnection;
import org.apache.http.impl.DefaultHttpParams;
import org.apache.http.impl.io.PlainSocketFactory;
import org.apache.http.message.HttpPost;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.protocol.HttpRequestExecutor;
import org.apache.http.protocol.RequestContent;
import org.apache.http.protocol.RequestTargetHost;
import org.apache.http.util.EntityUtils;
import org.jsnap.db.base.DbParam;
import org.jsnap.exception.JSnapException;
import org.jsnap.exception.UnhandledException;
import org.jsnap.exception.comm.CommunicationException;
import org.jsnap.exception.comm.MalformedRequestException;
import org.jsnap.exception.security.AccessDeniedException;
import org.jsnap.exception.security.LoginFailedException;
import org.jsnap.exception.security.SecurityException;
import org.jsnap.http.base.HttpParameters;
import org.jsnap.http.base.HttpReject;
import org.jsnap.http.base.HttpResponse;
import org.jsnap.http.base.HttpServerConnection;
import org.jsnap.http.base.HttpServlet;
import org.jsnap.response.Formatter;
import org.jsnap.response.Response;
import org.jsnap.security.Credentials;
import org.jsnap.util.JUtility;

public class HttpRequest extends DbRequest {
    private static final long serialVersionUID = 2042340568872727567L;

    protected static final String EXECUTE_REQUEST_URI = "/execute";

    private static final String COMMAND = "command";
    private static final String DATABASE = "database";
    private static final String SQL = "sql";
    private static final String FORMATTER = "formatter";
    private static final String TIMEOUT = "timeout";
    private static final String KEEPALIVE = "keepalive";
    private static final String ZIP = "zip";
    private static final String KEY = "key";
    private static final String FROM = "from";
    private static final String TO = "to";
    private static final String MAXROWS = "maxrows";
    public static final String USERNAME = "username";
    public static final String PASSWORD = "password";
    private static final String MAXPARAMETERS = "maxparameters";
    private static final String PARAMETER = "parameter";
    private static final String STATE = "state";
    private static final String TYPE = "type";
    private static final String OTHER = "other";
    private static final String VALUE = "value";
    private static final String ISNULL = "isnull";
    private static final String ON = "on";

    private static final int DEFAULT_HTTP_PORT = 80;
    private static final Scheme SCHEME = new Scheme("http", PlainSocketFactory.getSocketFactory(),
            DEFAULT_HTTP_PORT);

    protected Scheme getScheme() {
        return SCHEME;
    }

    // Used when instance is in client:
    private int port;
    private String host;
    private org.apache.http.HttpResponse response;
    // Used when instance is in server:
    private RequestHandler servlet;

    public HttpRequest() { // Default constructor required
        super(); // by the Listener class.
    }

    // Clients should use this constructor to create an instance of HttpRequest.
    public HttpRequest(String host, int port) {
        super(System.currentTimeMillis(), null); // Don't pass socket to lower levels; all socket
        this.host = host; // operations will be handled at this level.
        this.port = port;
        this.willHandleMyself = true; // All socket operations will be handled at this level.
    }

    // Clients should not use this to create an instance of HttpRequest. That is because
    // this constructor initiates the servlet object, which means that the instance is to
    // be used by the JSnap server to serve the request.
    private HttpRequest(long acceptedOn, Socket s) throws CommunicationException {
        super(acceptedOn, null); // Don't pass socket to lower levels; all socket
        this.willHandleMyself = true; // operations will be handled at this level.
        this.servlet = new RequestHandler(HttpServlet.bindSocketForHttp(s));
    }

    protected Request doCreate(long acceptedOn, Socket s)
            throws CommunicationException, SecurityException, MalformedRequestException {
        HttpRequest req = new HttpRequest(acceptedOn, s);
        req.servlet.service();
        // This call not only reads and unpacks the request but it also processes
        // the received request and sends the response back. Since everything is
        // complete when this call succeeds, the function returns null. Go to the
        // Listener class to see what happens when create returns null.
        return null;
    }

    protected void doReject(Socket s) {
        try {
            HttpReject reject = new HttpReject(HttpServlet.bindSocketForHttp(s));
            reject.service();
        } catch (CommunicationException ignore) {
            ignore.log(); // Hard to handle at this point, just log and ignore.
        }
    }

    protected void processResponse(Response resp) throws JSnapException {
        resp.asBytes(servlet.response.out);
        servlet.response.contentType = resp.getContentType();
        servlet.response.characterSet = resp.getCharacterSet();
        servlet.response.statusCode = HttpStatus.SC_OK; // HTTP.200
    }

    protected void processException(JSnapException ex) {
        ex.log();
        int statusCode = HttpStatus.SC_INTERNAL_SERVER_ERROR; // HTTP.500
        if (ex instanceof MalformedRequestException)
            statusCode = HttpStatus.SC_BAD_REQUEST; // HTTP.400
        else if (ex instanceof LoginFailedException || ex instanceof AccessDeniedException)
            statusCode = HttpStatus.SC_UNAUTHORIZED; // HTTP.401
        PrintStream ps = new PrintStream(servlet.response.out);
        ps.println("HTTP " + statusCode + " " + HttpStatus.getStatusText(statusCode));
        ps.println();
        ex.printStackTrace(ps);
        servlet.response.contentType = Formatter.PLAIN;
        servlet.response.characterSet = Formatter.DEFAULT;
        servlet.response.statusCode = statusCode;
    }

    public byte[] receive() throws JSnapException {
        return read();
    }

    protected byte[] read() throws CommunicationException {
        // This function gets called only for the client's reads. Server's
        // reads are performed by the RequestHandler.doServiceImpl() method.
        HttpEntity entity = response.getEntity();
        response = null; // Response has been read/consumed.
        try {
            return EntityUtils.toByteArray(entity);
        } catch (IOException e) {
            throw new CommunicationException(e);
        }
    }

    protected void write(byte[] packed) throws CommunicationException, SecurityException {
        // This function gets called only for the client's writes. Server's
        // writes are performed by the RequestHandler.doServiceImpl() method.
        HttpHost host = new HttpHost(this.host, this.port, getScheme());
        HttpClientConnection connection = new DefaultHttpClientConnection(host);
        HttpPost post = new HttpPost(EXECUTE_REQUEST_URI);
        post.setEntity(new ByteArrayEntity(packed));
        HttpRequestExecutor executor = new HttpRequestExecutor();
        executor.addInterceptor(new RequestContent());
        executor.addInterceptor(new RequestTargetHost());
        // This is a very reliable timeout mechanism for the _client_. Recall that
        // server's timeout mechanism depends on the JDBC driver. 
        if (timeout != 0) {
            int remaining = new Long((tryUntil + timeout) - System.currentTimeMillis()).intValue();
            if (remaining < 0)
                remaining = 1; // immediately!
            HttpParams params = new DefaultHttpParams();
            params.setIntParameter(HttpConnectionParams.SO_TIMEOUT, remaining);
            executor.setParams(params);
        }
        try {
            response = executor.execute(post, connection);
        } catch (IOException e) {
            throw new CommunicationException(e);
        } catch (HttpException e) {
            throw new CommunicationException(e);
        }
    }

    protected byte[] doPack() {
        try {
            String packed = COMMAND + "=" + URLEncoder.encode(JUtility.valueOf(command, ""), Formatter.DEFAULT)
                    + "&";
            packed += DATABASE + "=" + URLEncoder.encode(JUtility.valueOf(database, ""), Formatter.DEFAULT) + "&";
            packed += SQL + "=" + URLEncoder.encode(JUtility.valueOf(sql, ""), Formatter.DEFAULT) + "&";
            packed += FORMATTER + "=" + URLEncoder.encode(JUtility.valueOf(formatter, ""), Formatter.DEFAULT) + "&";
            packed += TIMEOUT + "=" + Long.toString(timeout) + "&";
            packed += KEEPALIVE + "=" + Long.toString(keepalive) + "&";
            packed += ZIP + "=" + Integer.toString(zip) + "&";
            packed += KEY + "=" + Integer.toString(key) + "&";
            packed += FROM + "=" + Integer.toString(from) + "&";
            packed += TO + "=" + Integer.toString(to) + "&";
            packed += MAXROWS + "=" + Integer.toString(maxrows) + "&";
            String[] credentialsArray = credentials.get();
            packed += USERNAME + "=" + URLEncoder.encode(credentialsArray[0], Formatter.DEFAULT) + "&";
            packed += PASSWORD + "=" + URLEncoder.encode(credentialsArray[1], Formatter.DEFAULT) + "&";
            packed += MAXPARAMETERS + "=" + Integer.toString(parameters.size());
            for (int i = 1; i <= parameters.size(); ++i) {
                DbParam p = parameters.get(i - 1);
                String parameterName = PARAMETER + i;
                packed += "&" + parameterName + STATE + "=" + ON + "&";
                packed += parameterName + TYPE + "=" + Integer.toString(p.type) + "&";
                packed += parameterName + OTHER + "=" + URLEncoder.encode(p.other, Formatter.DEFAULT) + "&";
                packed += parameterName + VALUE + "=" + URLEncoder.encode(p.value, Formatter.DEFAULT) + "&";
                if (p.isNull)
                    packed += parameterName + ISNULL + "=" + Boolean.toString(p.isNull);
            }
            return packed.getBytes();
        } catch (UnsupportedEncodingException e) {
            new UnhandledException(e).log(); // Logged but ignored. Highly unlikely.
            return new byte[0];
        }
    }

    protected void doUnpack(byte[] packed, int offset, int length) {
        String s = new String(packed, offset, length);
        HttpParameters parameters = org.jsnap.http.base.HttpRequest.parseInto(s, '=', '&');
        Credentials credentials = org.jsnap.http.base.HttpRequest.extractCredentials(parameters);
        doUnpack(parameters, credentials);
    }

    private void doUnpack(HttpParameters parameters, Credentials credentials) {
        command = JUtility.valueOf(parameters.get(COMMAND), "");
        database = JUtility.valueOf(parameters.get(DATABASE), "");
        sql = JUtility.valueOf(parameters.get(SQL), "");
        formatter = JUtility.valueOf(parameters.get(FORMATTER), "");
        timeout = JUtility.valueOf(parameters.get(TIMEOUT), -1L);
        keepalive = JUtility.valueOf(parameters.get(KEEPALIVE), -1L);
        zip = JUtility.valueOf(parameters.get(ZIP), -1);
        key = JUtility.valueOf(parameters.get(KEY), -1);
        from = JUtility.valueOf(parameters.get(FROM), -1);
        to = JUtility.valueOf(parameters.get(TO), -1);
        maxrows = JUtility.valueOf(parameters.get(MAXROWS), -1);
        this.credentials = credentials;
        int maxParameters = JUtility.valueOf(parameters.get(MAXPARAMETERS), 0);
        for (int i = 1; i <= maxParameters; ++i) {
            String parameterName = PARAMETER + i;
            String parameterState = parameters.get(parameterName + STATE);
            if (parameterState.equals(ON)) {
                int type = JUtility.valueOf(parameters.get(parameterName + TYPE), DbParam.STRING);
                String other = (type == DbParam.VENDOR_SPECIFIC ? parameters.get(parameterName + OTHER) : "");
                String value = parameters.get(parameterName + VALUE);
                boolean isNull = (parameters.get(parameterName + ISNULL) != null);
                this.parameters.add(new DbParam(type, other, value, isNull));
            }
        }
    }

    private void trim() {
        command = command.trim();
        database = database.trim();
        sql = sql.trim();
        formatter = formatter.trim();
    }

    private class RequestHandler extends HttpServlet {
        private HttpResponse response;

        public RequestHandler(HttpServerConnection c) {
            super(c);
        }

        protected void doServiceImpl(org.jsnap.http.base.HttpRequest request, HttpResponse response) {
            this.response = response;
            try {
                if (request.uri.equals(EXECUTE_REQUEST_URI) == false)
                    throw new MalformedRequestException("Requests are welcome only at " + EXECUTE_REQUEST_URI);
                doUnpack(request.parameters, request.credentials);
                check();
                trim();
                run();
            } catch (JSnapException e) {
                processException(e);
            } catch (Throwable t) {
                processException(new UnhandledException(t));
            }
            response.zipSize = (zip < 0 ? 0 : zip);
        }
    }
}