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

io.soluble.pjb.servlet.fastcgi.FastCGIServlet Maven / Gradle / Ivy

The newest version!
/*-*- mode: Java; tab-width:8 -*-*/
package io.soluble.pjb.servlet.fastcgi;

/*
 * Copyright (C) 2003-2007 Jost Boekemeier
 *
 * 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 COPYRIGHT HOLDER(S) OR AUTHOR(S) 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.
 */

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;

import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import io.soluble.pjb.bridge.Util;
import io.soluble.pjb.bridge.http.AbstractChannelName;
import io.soluble.pjb.bridge.http.FCGIConnectException;
import io.soluble.pjb.bridge.http.FCGIConnectionException;
import io.soluble.pjb.bridge.http.FCGIConnectionPool;
import io.soluble.pjb.bridge.http.FCGIInputStream;
import io.soluble.pjb.bridge.http.FCGIOutputStream;
import io.soluble.pjb.bridge.http.FCGIUtil;
import io.soluble.pjb.bridge.http.IContextFactory;
import io.soluble.pjb.servlet.ContextLoaderListener;
import io.soluble.pjb.servlet.PhpJavaServlet;
import io.soluble.pjb.servlet.ServletContextFactory;
import io.soluble.pjb.servlet.ServletUtil;

/**
 * A CGI Servlet which connects to a FastCGI server. If allowed by the
 * administrator and if a fast cgi binary is installed in the JavaBridge web application or
 * DEFAULT_CGI_LOCATIONS, the bridge can automatically start one FCGI
 * server on the computer. Default is Autostart.
 * 

The admin may start a FCGI * server for all users with the command:
cd /tmp
* REDIRECT_STATUS=200 X_JAVABRIDGE_OVERRIDE_HOSTS="/" PHP_FCGI_CHILDREN="5" * PHP_FCGI_MAX_REQUESTS="5000" /usr/bin/php-cgi -b 127.0.0.1:9667
*
*

*

When the program /bin/sh does not exist, a program called launcher.exe * is called instead: * launcher.exe -a "path_to_php-cgi.exe" -b 9667.

* * @author jostb * @see io.soluble.pjb.bridge.Util#DEFAULT_CGI_LOCATIONS */ public class FastCGIServlet extends HttpServlet { protected static final String _80 = "80"; protected static final String _443 = "443"; private static final long serialVersionUID = 3545800996174312757L; protected String documentRoot; protected String serverSignature; protected static class Environment { public IContextFactory ctx; public String contextPath; public String pathInfo; public String servletPath; public String queryString; public String requestUri; public HashMap environment; public boolean includedJava; public ArrayList allHeaders; } protected ServletContext context; protected ContextLoaderListener contextLoaderListener; protected String serverInfo; protected FCGIConnectionPool connectionPool; protected boolean phpRequestURIisUnique; // Patch#3040849 /** * Create a new FastCGI servlet which connects to a PHP FastCGI server using a connection pool. *

* If the JavaBridge context exists and the JavaBridge context can * start a FastCGI server and the current context is configured to * connect to a FastCGI server, the current context connects to * the JavaBridge context to start the server and then uses this * server for all subsequent requests until the server is * stopped. When FastCGI is not available (anymore), the parent * CGI servlet is used instead. * * @param config The servlet config * @throws ServletException * @see io.soluble.pjb.bridge.http.FCGIConnectionPool * @see #destroy() */ @Override public void init(ServletConfig config) throws ServletException { super.init(config); context = config.getServletContext(); String value = (String) config.getInitParameter("php_request_uri_is_unique"); if ("on".equalsIgnoreCase(value) || "true".equalsIgnoreCase(value)) phpRequestURIisUnique = true; contextLoaderListener = (ContextLoaderListener) context.getAttribute(ContextLoaderListener.CONTEXT_LOADER_LISTENER); serverInfo = config.getServletName(); if (serverInfo == null) serverInfo = "FastCGIServlet"; documentRoot = ServletUtil.getRealPath(context, ""); serverSignature = context.getServerInfo(); connectionPool = contextLoaderListener.getConnectionPool(); if (connectionPool == null) { try { contextLoaderListener.getChannelName().test(); } catch (FCGIConnectException e) { throw new ServletException(e); } throw new ServletException("No connection pool"); } } /** * Destroys the FastCGI connection pool, if it exists. */ @Override public void destroy() { super.destroy(); } protected void setupRequestVariables(HttpServletRequest req, Environment env) { env.allHeaders = new ArrayList(); env.includedJava = contextLoaderListener.getPhpIncludeJava() && PhpJavaServlet.getHeader(Util.X_JAVABRIDGE_INCLUDE, req) == null; env.contextPath = (String) req.getAttribute("javax.servlet.include.context_path"); if (env.contextPath == null) env.contextPath = req.getContextPath(); env.pathInfo = (String) req.getAttribute("javax.servlet.include.path_info"); if (env.pathInfo == null) env.pathInfo = req.getPathInfo(); env.servletPath = (String) req.getAttribute("javax.servlet.include.servlet_path"); if (env.servletPath == null) env.servletPath = req.getServletPath(); env.queryString = (String) req.getAttribute("javax.servlet.include.query_string"); if (env.queryString == null) env.queryString = req.getQueryString(); if (phpRequestURIisUnique) { // use target: my.jsp:include||forward target.php => REQUEST_URI: target.php env.requestUri = (String) req.getAttribute("javax.servlet.include.request_uri"); } else { // use source: my.jsp:include||forward target.php => REQUEST_URI: my.jsp env.requestUri = (String) req.getAttribute("javax.servlet.forward.request_uri"); } if (env.requestUri == null) env.requestUri = req.getRequestURI(); } /** * calculate PATH_INFO, PATH_TRANSLATED and SCRIPT_FILENAME */ protected void setPathInfo(HttpServletRequest req, HashMap envp, Environment env) { String pathInfo = env.pathInfo; if (pathInfo != null) { envp.put("PATH_INFO", pathInfo); envp.put("PATH_TRANSLATED", documentRoot + pathInfo); } if (env.includedJava) envp.put("SCRIPT_FILENAME", ServletUtil.getRealPath(context, "java/JavaProxy.php")); else envp.put("SCRIPT_FILENAME", ServletUtil.getRealPath(context, env.servletPath)); } protected void setupCGIEnvironment(HttpServletRequest req, HttpServletResponse res, Environment env) throws ServletException { HashMap envp = (HashMap) contextLoaderListener.getEnvironment().clone(); envp.put("SERVER_SOFTWARE", serverInfo); envp.put("SERVER_NAME", ServletUtil.nullsToBlanks(req.getServerName())); envp.put("GATEWAY_INTERFACE", "CGI/1.1"); envp.put("SERVER_PROTOCOL", ServletUtil.nullsToBlanks(req.getProtocol())); int port = ServletUtil.getLocalPort(req); Integer iPort = (port == 0 ? -1 : port); envp.put("SERVER_PORT", iPort.toString()); envp.put("REQUEST_METHOD", ServletUtil.nullsToBlanks(req.getMethod())); envp.put("SCRIPT_NAME", env.contextPath + env.servletPath); envp.put("QUERY_STRING", ServletUtil.nullsToBlanks(env.queryString)); envp.put("REMOTE_HOST", ServletUtil.nullsToBlanks(req.getRemoteHost())); envp.put("REMOTE_ADDR", ServletUtil.nullsToBlanks(req.getRemoteAddr())); envp.put("AUTH_TYPE", ServletUtil.nullsToBlanks(req.getAuthType())); envp.put("REMOTE_USER", ServletUtil.nullsToBlanks(req.getRemoteUser())); envp.put("REMOTE_IDENT", ""); //not necessary for full compliance envp.put("CONTENT_TYPE", ServletUtil.nullsToBlanks(req.getContentType())); setPathInfo(req, envp, env); /* Note CGI spec says CONTENT_LENGTH must be NULL ("") or undefined * if there is no content, so we cannot put 0 or -1 in as per the * Servlet API spec. */ int contentLength = req.getContentLength(); String sContentLength = (contentLength <= 0 ? "" : (new Integer(contentLength)).toString()); envp.put("CONTENT_LENGTH", sContentLength); Enumeration headers = req.getHeaderNames(); String header; StringBuffer buffer = new StringBuffer(); while (headers.hasMoreElements()) { header = ((String) headers.nextElement()).toUpperCase(); if ("AUTHORIZATION".equalsIgnoreCase(header) || "PROXY_AUTHORIZATION".equalsIgnoreCase(header)) { //NOOP per CGI specification section 11.2 } else if ("HOST".equalsIgnoreCase(header)) { String host = req.getHeader(header); int idx = host.indexOf(":"); if (idx < 0) idx = host.length(); envp.put("HTTP_" + header.replace('-', '_'), host.substring(0, idx)); } else if (header.startsWith("X_")) { envp.put(header, req.getHeader(header)); } else { envp.put("HTTP_" + header.replace('-', '_'), ServletUtil.getHeaders(buffer, req.getHeaders(header))); } } env.environment = envp; if (env.includedJava) { env.environment.put("X_JAVABRIDGE_INCLUDE_ONLY", "1"); env.environment.put("X_JAVABRIDGE_INCLUDE", ServletUtil.getRealPath(getServletContext(), env.servletPath)); } env.environment.put("REDIRECT_STATUS", "200"); env.environment.put("SERVER_SOFTWARE", Util.EXTENSION_NAME); String sPort = (String) env.environment.get("SERVER_PORT"); String standardPort = req.isSecure() ? _443 : _80; StringBuilder httpHost = new StringBuilder((String) env.environment.get("SERVER_NAME")); if (!standardPort.equals(sPort)) { // append port only if necessary, see Patch#3040838 httpHost.append(":"); httpHost.append(sPort); } env.environment.put("HTTP_HOST", httpHost.toString()); String remotePort; try { remotePort = String.valueOf(req.getRemotePort()); } catch (Throwable t) { remotePort = String.valueOf(t); } env.environment.put("REMOTE_PORT", remotePort); String query = env.queryString; if (query != null) env.environment.put("REQUEST_URI", ServletUtil.nullsToBlanks(env.requestUri + "?" + query)); else env.environment.put("REQUEST_URI", ServletUtil.nullsToBlanks(env.requestUri)); env.environment.put("SERVER_ADDR", req.getServerName()); env.environment.put("SERVER_SIGNATURE", serverSignature); env.environment.put("DOCUMENT_ROOT", documentRoot); if (req.isSecure()) env.environment.put("HTTPS", "On"); /* send the session context now, otherwise the client has to * call handleRedirectConnection */ String id = PhpJavaServlet.getHeader(Util.X_JAVABRIDGE_CONTEXT, req); if (id == null) { id = (env.ctx = ServletContextFactory.addNew(contextLoaderListener.getContextServer(), this, getServletContext(), req, req, res)).getId(); // short path S1: no PUT request AbstractChannelName channelName = contextLoaderListener.getContextServer().getChannelName(env.ctx); if (channelName != null) { env.environment.put(Util.X_JAVABRIDGE_REDIRECT, channelName.getName()); env.ctx.getBridge(); contextLoaderListener.getContextServer().start(channelName, contextLoaderListener.getLogger()); } } env.environment.put(Util.X_JAVABRIDGE_CONTEXT, id); } /** * Optimized run method for FastCGI. Makes use of the large FCGI_BUF_SIZE and the specialized in.read(). * It is a modified copy of the parseBody. * * @see io.soluble.pjb.bridge.http.HeaderParser#parseBody(byte[], java.io.InputStream, io.soluble.pjb.bridge.http.OutputStreamFactory, io.soluble.pjb.bridge.http.HeaderParser) */ protected void parseBody(HttpServletRequest req, HttpServletResponse res, Environment env) throws FCGIConnectionException, FCGIConnectException, IOException, ServletException { final byte[] buf = new byte[FCGIUtil.FCGI_BUF_SIZE];// headers cannot be larger than this value! InputStream in; OutputStream out; FCGIInputStream natIn = null; FCGIOutputStream natOut = null; FCGIConnectionPool.Connection connection = null; try { connection = connectionPool.openConnection(); natOut = (FCGIOutputStream) connection.getOutputStream(); natIn = (FCGIInputStream) connection.getInputStream(); in = req.getInputStream(); // do not close in, otherwise requestDispatcher().include() will receive a closed input stream out = ServletUtil.getServletOutputStream(res); // send the FCGI header natOut.writeBegin(); natOut.writeParams(env.environment); String line; int i = 0, n, s = 0; boolean eoh = false; boolean rn = false; // the post variables if (("chunked".equalsIgnoreCase(PhpJavaServlet.getHeader("Transfer-Encoding", req))) || ("upgrade".equalsIgnoreCase(PhpJavaServlet.getHeader("Connection", req)))) { // write the post data while reading the response // used by either http/1.1 chunked connections or "WebSockets", // see http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-70 final InputStream inputStream = in; //in = null; final FCGIOutputStream natOutputStream = natOut; natOut = null; (new Thread() { @Override public void run() { int n; try { while ((n = inputStream.read(buf)) != -1) { natOutputStream.write(FCGIUtil.FCGI_STDIN, buf, n); } natOutputStream.write(FCGIUtil.FCGI_STDIN, FCGIUtil.FCGI_EMPTY_RECORD); } catch (IOException e) { e.printStackTrace(); } finally { try { natOutputStream.close(); } catch (IOException e) { } } } }).start(); } else { // write the post data before reading the response while ((n = in.read(buf)) != -1) { natOut.write(FCGIUtil.FCGI_STDIN, buf, n); } natOut.write(FCGIUtil.FCGI_STDIN, FCGIUtil.FCGI_EMPTY_RECORD); natOut.close(); natOut = null; } // the header and content String remain = null; while ((n = natIn.read(buf)) != -1) { int N = i + n; // header while (!eoh && i < N) { switch (buf[i++]) { case '\n': if (rn) { eoh = true; } else { if (remain != null) { line = remain + new String(buf, s, i - s, Util.ASCII); line = line.substring(0, line.length() - 2); remain = null; } else { line = new String(buf, s, i - s - 2, Util.ASCII); } addHeader(res, line, env); s = i; } rn = true; break; case '\r': break; default: rn = false; } } // body if (eoh) { if (i < N) out.write(buf, i, N - i); } else { if (remain != null) { remain += new String(buf, s, i - s, Util.ASCII); } else { remain = new String(buf, s, i - s, Util.ASCII); } } s = i = 0; } natIn.close(); String phpFatalError = natIn.checkError(); StringBuffer phpError = natIn.getError(); if ((phpError != null) && (Util.logLevel > 4)) Util.logDebug(phpError.toString()); natIn = null; connection = null; if (phpFatalError != null) throw new RuntimeException(phpFatalError); } catch (InterruptedException e) { throw new ServletException(e); } finally { // Destroy physical connection if exception occured, // so that the PHP side doesn't keep unsent data // A more elegant approach would be to use the FCGI ABORT request. if (connection != null) connection.setIsClosed(); if (natIn != null) try { natIn.close(); } catch (IOException e) { } if (natOut != null) try { natOut.close(); } catch (IOException e) { } } } protected Environment getEnvironment() { return new Environment(); } protected void execute(HttpServletRequest req, HttpServletResponse res) throws IOException, ServletException, InterruptedException { Environment env = getEnvironment(); setupRequestVariables(req, env); setupCGIEnvironment(req, res, env); try { parseBody(req, res, env); } catch (FCGIConnectException ex) { if (Util.logLevel > 1) { Util.logDebug("PHP FastCGI server failed: " + ex); Util.printStackTrace(ex); } throw new IOException("PHP FastCGI server failed: ", ex); } catch (FCGIConnectionException x) { Util.logError("PHP application terminated unexpectedly, have you started php-cgi with the environment setting PHP_FCGI_MAX_REQUESTS=" + contextLoaderListener.getPhpMaxRequests() + "? Error: " + x); if (Util.logLevel > 1) { Util.logDebug("PHP FastCGI instance failed: " + x); Util.printStackTrace(x); } throw new ServletException("PHP FastCGI instance failed.", x); } catch (IOException e) { // ignore client abort exception if (Util.logLevel > 4) Util.printStackTrace(e); } finally { if (env.ctx != null) env.ctx.releaseManaged(); env.ctx = null; } } protected void addHeader(HttpServletResponse response, String line, Environment env) { try { if (line.startsWith("Status")) { line = line.substring(line.indexOf(":") + 1).trim(); int i = line.indexOf(' '); if (i > 0) line = line.substring(0, i); response.setStatus(Integer.parseInt(line)); } else { if (!env.allHeaders.contains(line)) { response.addHeader (line.substring(0, line.indexOf(":")).trim(), line.substring(line.indexOf(":") + 1).trim()); env.allHeaders.add(line); } } } catch (ArrayIndexOutOfBoundsException | StringIndexOutOfBoundsException e) {/*not a valid header*/} /*not a valid header*/ } protected void handle(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { try { execute(req, res); } catch (IOException e) { try { res.reset(); } catch (Exception ex) {/*ignore*/} StringBuilder buf = new StringBuilder("PHP FastCGI server not running. Please see server log for details."); if (contextLoaderListener.getChannelName() != null && context != null) { buf.append(" Or start a PHP FastCGI server using the command:\n"); buf.append(contextLoaderListener.getChannelName() .getFcgiStartCommand(ServletUtil.getRealPath( context, ContextLoaderListener.CGI_DIR), contextLoaderListener.getPhpMaxRequests())); } throw new IOException(buf.toString(), e); } catch (ServletException e) { try { res.reset(); } catch (Exception ex) {/*ignore*/} throw e; } catch (Throwable t) { try { res.reset(); } catch (Exception ex) {/*ignore*/} if (Util.logLevel > 4) Util.printStackTrace(t); throw new ServletException(t); } } @Override protected void doPut(HttpServletRequest req, HttpServletResponse res) throws IOException, ServletException { handle(req, res); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse res) throws IOException, ServletException { handle(req, res); } @Override protected void doDelete(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { handle(req, res); } @Override protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { if (Util.logLevel > 4) { if (req.getAttribute("javax.servlet.include.request_uri") != null) log("doGet (included):" + req.getAttribute("javax.servlet.include.request_uri")); log("doGet:" + req.getRequestURI()); } handle(req, res); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy