added new cgiservlet based on jetty implementation

This commit is contained in:
Sebastian Sdorra
2010-09-28 18:58:50 +02:00
parent fa41470032
commit 4e238edec0
3 changed files with 603 additions and 0 deletions

View File

@@ -0,0 +1,382 @@
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package sonia.scm.web.cgi;
//~--- non-JDK imports --------------------------------------------------------
import sonia.scm.util.Util;
//~--- JDK imports ------------------------------------------------------------
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Enumeration;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Based on org.eclipse.jetty.servlets.CGI
*
* @author Sebastian Sdorra
*
*/
public class CGIRunner
{
/** Field description */
public static final int BUFFERSIZE = 2 * 8192;
/** Field description */
private static final Logger logger =
Logger.getLogger(CGIRunner.class.getName());
//~--- constructors ---------------------------------------------------------
/**
* Constructs ...
*
*
* @param context
* @param environment
* @param cmdPrefix
* @param ignoreExitState
*/
public CGIRunner(ServletContext context, EnvList environment,
String cmdPrefix, boolean ignoreExitState)
{
this.context = context;
this.environment = environment;
this.cmdPrefix = cmdPrefix;
this.ignoreExitState = ignoreExitState;
}
//~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @param command
* @param pathInfo
* @param req
* @param res
*
* @throws IOException
*/
public void exec(File command, String pathInfo, HttpServletRequest req,
HttpServletResponse res)
throws IOException
{
String path = command.getAbsolutePath();
File dir = command.getParentFile();
String scriptName = req.getRequestURI().substring(0,
req.getRequestURI().length() - pathInfo.length());
String scriptPath = context.getRealPath(scriptName);
String pathTranslated = req.getPathTranslated();
int len = req.getContentLength();
if (len < 0)
{
len = 0;
}
if ((pathTranslated == null) || (pathTranslated.length() == 0))
{
pathTranslated = path;
}
EnvList env = new EnvList(environment);
// these ones are from "The WWW Common Gateway Interface Version 1.1"
// look at :
// http://Web.Golux.Com/coar/cgi/draft-coar-cgi-v11-03-clean.html#6.1.1
env.set("AUTH_TYPE", req.getAuthType());
env.set("CONTENT_LENGTH", Integer.toString(len));
env.set("CONTENT_TYPE", req.getContentType());
env.set("GATEWAY_INTERFACE", "CGI/1.1");
if ((pathInfo != null) && (pathInfo.length() > 0))
{
env.set("PATH_INFO", pathInfo);
}
env.set("PATH_TRANSLATED", pathTranslated);
env.set("QUERY_STRING", req.getQueryString());
env.set("REMOTE_ADDR", req.getRemoteAddr());
env.set("REMOTE_HOST", req.getRemoteHost());
// The identity information reported about the connection by a
// RFC 1413 [11] request to the remote agent, if
// available. Servers MAY choose not to support this feature, or
// not to request the data for efficiency reasons.
// "REMOTE_IDENT" => "NYI"
env.set("REMOTE_USER", req.getRemoteUser());
env.set("REQUEST_METHOD", req.getMethod());
env.set("SCRIPT_NAME", scriptName);
env.set("SCRIPT_FILENAME", scriptPath);
env.set("SERVER_NAME", req.getServerName());
env.set("SERVER_PORT", Integer.toString(req.getServerPort()));
env.set("SERVER_PROTOCOL", req.getProtocol());
env.set("SERVER_SOFTWARE", context.getServerInfo());
Enumeration enm = req.getHeaderNames();
while (enm.hasMoreElements())
{
String name = (String) enm.nextElement();
String value = req.getHeader(name);
env.set("HTTP_" + name.toUpperCase().replace('-', '_'), value);
}
// these extra ones were from printenv on www.dev.nomura.co.uk
env.set("HTTPS", (req.isSecure()
? "ON"
: "OFF"));
// "DOCUMENT_ROOT" => root + "/docs",
// "SERVER_URL" => "NYI - http://us0245",
// "TZ" => System.getProperty("user.timezone"),
// are we meant to decode args here ? or does the script get them
// via PATH_INFO ? if we are, they should be decoded and passed
// into exec here...
String execCmd = path;
if ((execCmd.charAt(0) != '"') && (execCmd.indexOf(" ") >= 0))
{
execCmd = "\"" + execCmd + "\"";
}
if (cmdPrefix != null)
{
execCmd = cmdPrefix + " " + execCmd;
}
Process p = (dir == null)
? Runtime.getRuntime().exec(execCmd, env.getEnvArray())
: Runtime.getRuntime().exec(execCmd, env.getEnvArray(), dir);
// hook processes input to browser's output (async)
final InputStream inFromReq = req.getInputStream();
final OutputStream outToCgi = p.getOutputStream();
final int inLength = len;
// TODO: print or log error stream
// IO.copyThread(p.getErrorStream(), System.err);
new Thread(new Runnable()
{
@Override
public void run()
{
try
{
if (inLength > 0)
{
copy(inFromReq, outToCgi, inLength);
}
outToCgi.close();
}
catch (IOException e)
{
logger.log(Level.FINEST, null, e);
}
}
}).start();
// hook processes output to browser's input (sync)
// if browser closes stream, we should detect it and kill process...
OutputStream os = null;
try
{
// read any headers off the top of our input stream
// NOTE: Multiline header items not supported!
String line = null;
InputStream inFromCgi = p.getInputStream();
// br=new BufferedReader(new InputStreamReader(inFromCgi));
// while ((line=br.readLine())!=null)
while ((line = getTextLineFromStream(inFromCgi)).length() > 0)
{
if (!line.startsWith("HTTP"))
{
int k = line.indexOf(':');
if (k > 0)
{
String key = line.substring(0, k).trim();
String value = line.substring(k + 1).trim();
if ("Location".equals(key))
{
res.sendRedirect(res.encodeRedirectURL(value));
}
else if ("Status".equals(key))
{
String[] token = value.split(" ");
int status = Integer.parseInt(token[0]);
res.setStatus(status);
}
else
{
// add remaining header items to our response header
res.addHeader(key, value);
}
}
}
}
// copy cgi content to response stream...
os = res.getOutputStream();
Util.copy(inFromCgi, os);
p.waitFor();
if (!ignoreExitState)
{
int exitValue = p.exitValue();
if (0 != exitValue)
{
StringBuilder msg = new StringBuilder("Non-zero exit status (");
msg.append(exitValue).append(") from CGI program: ").append(path);
logger.warning(msg.toString());
if (!res.isCommitted())
{
res.sendError(500, "Failed to exec CGI");
}
}
}
}
catch (IOException e)
{
// browser has probably closed its input stream - we
// terminate and clean up...
logger.finest("CGI: Client closed connection!");
}
catch (InterruptedException ie)
{
logger.finest("CGI: interrupted!");
}
finally
{
if (os != null)
{
Util.close(os);
}
os = null;
p.destroy();
// Log.debug("CGI: terminated!");
}
}
/**
* Method description
*
*
* @param in
* @param out
* @param byteCount
*
* @throws IOException
*/
private void copy(InputStream in, OutputStream out, long byteCount)
throws IOException
{
byte buffer[] = new byte[BUFFERSIZE];
int len = BUFFERSIZE;
if (byteCount >= 0)
{
while (byteCount > 0)
{
int max = (byteCount < BUFFERSIZE)
? (int) byteCount
: BUFFERSIZE;
len = in.read(buffer, 0, max);
if (len == -1)
{
break;
}
byteCount -= len;
out.write(buffer, 0, len);
}
}
else
{
while (true)
{
len = in.read(buffer, 0, BUFFERSIZE);
if (len < 0)
{
break;
}
out.write(buffer, 0, len);
}
}
}
//~--- get methods ----------------------------------------------------------
/**
* Method description
*
*
* @param is
*
* @return
*
* @throws IOException
*/
private String getTextLineFromStream(InputStream is) throws IOException
{
StringBuilder buffer = new StringBuilder();
int b;
while ((b = is.read()) != -1 && (b != (int) '\n'))
{
buffer.append((char) b);
}
return buffer.toString().trim();
}
//~--- fields ---------------------------------------------------------------
/** Field description */
private String cmdPrefix;
/** Field description */
private ServletContext context;
/** Field description */
private EnvList environment;
/** Field description */
private boolean ignoreExitState;
}

View File

@@ -0,0 +1,127 @@
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package sonia.scm.web.cgi;
//~--- non-JDK imports --------------------------------------------------------
import sonia.scm.util.Util;
//~--- JDK imports ------------------------------------------------------------
import java.io.File;
import java.io.IOException;
import java.util.Enumeration;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* CGI Servlet.
*
* The cgi bin directory can be set with the "cgibinResourceBase" init parameter
* or it will default to the resource base of the context.
*
* The "commandPrefix" init parameter may be used to set a prefix to all
* commands passed to exec. This can be used on systems that need assistance to
* execute a particular file type. For example on windows this can be set to
* "perl" so that perl scripts are executed.
*
* The "Path" init param is passed to the exec environment as PATH. Note: Must
* be run unpacked somewhere in the filesystem.
*
* Any initParameter that starts with ENV_ is used to set an environment
* variable with the name stripped of the leading ENV_ and using the init
* parameter value.
*
* Based on org.eclipse.jetty.servlets.CGI
*
* @author Sebastian Sdorra
*
*/
public class CGIServlet extends HttpServlet
{
/** Field description */
private static final long serialVersionUID = 5719539505555835833L;
//~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @throws ServletException
*/
@Override
public void init() throws ServletException
{
EnvList env = new EnvList();
String cmdPrefix = getInitParameter("commandPrefix");
boolean ignoreExitStatus =
"true".equalsIgnoreCase(getInitParameter("ignoreExitState"));
String commandPath = getInitParameter("command");
if (Util.isNotEmpty(commandPath))
{
command = new File(commandPath);
}
Enumeration e = getInitParameterNames();
while (e.hasMoreElements())
{
String n = (String) e.nextElement();
if ((n != null) && n.startsWith("ENV_"))
{
env.set(n.substring(4), getInitParameter(n));
}
}
if (!env.containsKey("SystemRoot"))
{
String os = System.getProperty("os.name");
if ((os != null) && (os.toLowerCase().indexOf("windows") != -1))
{
env.set("SystemRoot", "C:\\WINDOWS");
}
}
cgiRunner = new CGIRunner(getServletContext(), env, cmdPrefix,
ignoreExitStatus);
}
/**
* Method description
*
*
* @param req
* @param resp
*
* @throws IOException
* @throws ServletException
*/
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
{
cgiRunner.exec(command, req.getPathInfo(), req, resp);
}
//~--- fields ---------------------------------------------------------------
/** Field description */
private CGIRunner cgiRunner;
/** Field description */
private File command;
}

View File

@@ -0,0 +1,94 @@
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package sonia.scm.web.cgi;
//~--- non-JDK imports --------------------------------------------------------
import sonia.scm.util.Util;
//~--- JDK imports ------------------------------------------------------------
import java.util.HashMap;
import java.util.Map;
/**
*
* @author Sebastian Sdorra
*/
public class EnvList
{
/**
* Constructs ...
*
*/
EnvList()
{
envMap = new HashMap<String, String>();
}
/**
* Constructs ...
*
*
* @param l
*/
EnvList(EnvList l)
{
envMap = new HashMap<String, String>(l.envMap);
}
//~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @return
*/
@Override
public String toString()
{
return envMap.toString();
}
public boolean containsKey( String key )
{
return envMap.containsKey(key);
}
//~--- get methods ----------------------------------------------------------
/**
* Get representation suitable for passing to exec.
*
* @return
*/
public String[] getEnvArray()
{
return envMap.values().toArray(new String[envMap.size()]);
}
//~--- set methods ----------------------------------------------------------
/**
* Set a name/value pair, null values will be treated as an empty String
*
* @param name
* @param value
*/
public void set(String name, String value)
{
envMap.put(name, name.concat("=").concat(Util.nonNull(value)));
}
//~--- fields ---------------------------------------------------------------
/** Field description */
private Map<String, String> envMap;
}