From: roberts Date: Sun, 31 Jan 1999 02:45:49 +0000 (+0000) Subject: Files moved up from the old src directory. X-Git-Tag: 0.67_01~426 X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=commitdiff_plain;h=61962ef7ec4d21c9690944abc55705c4a8636d35;hp=89ac62e3d59c3d6d4eb5abbdf555a65080fb7c04;p=catagits%2Ffcgi2.git Files moved up from the old src directory. Moved Files: FCGIInterface.java FCGIInputStream.java FCGIGlobalDefs.java FCGIMessage.java FCGIOutputStream.java FCGIRequest.java --- diff --git a/java/FCGIGlobalDefs.java b/java/FCGIGlobalDefs.java new file mode 100644 index 0000000..cd0fa8a --- /dev/null +++ b/java/FCGIGlobalDefs.java @@ -0,0 +1,87 @@ +/* + * @(#)FCGIGlobalDefs.java + * + * + * FastCGi compatibility package Interface + * + * + * Copyright (c) 1996 Open Market, Inc. + * + * See the file "LICENSE.TERMS" for information on usage and redistribution + * of this file, and for a DISCLAIMER OF ALL WARRANTIES. + * + * $Id: FCGIGlobalDefs.java,v 1.1 1999/01/31 02:45:51 roberts Exp $ + */ + +/* This class contains FCGI global definitions corresponding to + * the #defs in the C version. + */ +import java.io.PrintStream; + +public abstract class FCGIGlobalDefs { + + public static final int def_FCGIMaxLen = 0xffff; + /* + * Define Length of FCGI message bodies in bytes + */ + public static final int def_FCGIHeaderLen = 8; + public static final int def_FCGIEndReqBodyLen = 8; + public static final int def_FCGIBeginReqBodyLen = 8; + public static final int def_FCGIUnknownBodyTypeBodyLen = 8; + /* + * Header defines + */ + public static int def_FCGIVersion1 = 1; + /* FCGI Record Types */ + public static final int def_FCGIBeginRequest = 1; + public static final int def_FCGIAbortRequest = 2; + public static final int def_FCGIEndRequest = 3; + public static final int def_FCGIParams = 4; + public static final int def_FCGIStdin = 5; + public static final int def_FCGIStdout = 6; + public static final int def_FCGIStderr = 7; + public static final int def_FCGIData = 8; + public static final int def_FCGIGetValues = 9; + public static final int def_FCGIGetValuesResult = 10; + public static final int def_FCGIUnknownType = 11; + public static final int def_FCGIMaxType = def_FCGIUnknownType; + /* Request ID Values */ + public static final int def_FCGINullRequestID = 0; + /* + * Begin Request defines + */ + /* Mask flags */ + public static int def_FCGIKeepConn = 1; + /* Roles */ + public static final int def_FCGIResponder = 1; + public static final int def_FCGIAuthorizer = 2; + public static final int def_FCGIFilter = 3; + /* + * End Request defines + */ + /* Protocol status */ + public static final int def_FCGIRequestComplete = 0; + public static final int def_FCGICantMpxConn = 1; + public static final int def_FCGIOverload = 2; + public static final int def_FCGIUnknownRole = 3; + /* + * Get Values, Get Values Results defines + */ + public static final String def_FCGIMaxConns = "FCGI_MAX_CONNS"; + public static final String def_FCGIMaxReqs = "FCGI_MAX_REQS"; + public static final String def_FCGIMpxsConns = "FCGI_MPXS_CONNS"; + /* + * Return codes for Process* functions + */ + public static final int def_FCGIStreamRecord = 0; + public static final int def_FCGISkip = 1; + public static final int def_FCGIBeginRecord = 2; + public static final int def_FCGIMgmtRecord = 3; + /* + * Error Codes + */ + public static final int def_FCGIUnsupportedVersion = -2; + public static final int def_FCGIProtocolError = -3; + public static final int def_FCGIParamsError = -4; + public static final int def_FCGICallSeqError = -5; +} diff --git a/java/FCGIInputStream.java b/java/FCGIInputStream.java new file mode 100644 index 0000000..4c2a1cc --- /dev/null +++ b/java/FCGIInputStream.java @@ -0,0 +1,380 @@ +/* + * @(#)FCGIInputStream.java + * + * FastCGi compatibility package Interface + * + * + * Copyright (c) 1996 Open Market, Inc. + * + * See the file "LICENSE.TERMS" for information on usage and redistribution + * of this file, and for a DISCLAIMER OF ALL WARRANTIES. + * + * $Id: FCGIInputStream.java,v 1.1 1999/01/31 02:45:49 roberts Exp $ + */ + +import java.io.*; +import FCGIRequest; +import FCGIGlobalDefs; + +/** + * This stream manages buffered reads of FCGI messages. + */ +public class FCGIInputStream extends InputStream { + + /* Stream vars */ + + public int rdNext; + public int stop; + public boolean isClosed; + + /* require methods to set, get and clear */ + private int errno; + private Exception errex; + + /* data vars */ + + public byte buff[]; + public int buffLen; + public int buffStop; + public int type; + public int contentLen; + public int paddingLen; + public boolean skip; + public boolean eorStop; + public FCGIRequest request; + + public InputStream in; + + + /** + * Creates a new input stream to manage fcgi prototcol stuff + * @param in the input stream bufLen length of buffer streamType + */ + public FCGIInputStream(FileInputStream inStream, int bufLen, + int streamType, + FCGIRequest inReq) { + + in = inStream; + buffLen = Math.min(bufLen,FCGIGlobalDefs.def_FCGIMaxLen); + buff = new byte[buffLen]; + type = streamType; + stop = rdNext = buffStop = 0; + isClosed = false; + contentLen = 0; + paddingLen = 0; + skip = false; + eorStop = false; + request = inReq; + + } + /** + * Reads a byte of data. This method will block if no input is + * available. + * @return the byte read, or -1 if the end of the + * stream is reached. + * @exception IOException If an I/O error has occurred. + */ + public int read() throws IOException { + if (rdNext != stop) { + return buff[rdNext++]; + } + if (isClosed){ + return -1; + } + fill(); + if (rdNext != stop){ + return buff[rdNext++]; + } + return -1; + } + /** + * Reads into an array of bytes. This method will + * block until some input is available. + * @param b the buffer into which the data is read + * @return the actual number of bytes read, -1 is + * returned when the end of the stream is reached. + * @exception IOException If an I/O error has occurred. + */ + public int read(byte b[]) throws IOException { + return read(b, 0, b.length); + } + + /** + * Reads into an array of bytes. + * Blocks until some input is available. + * @param b the buffer into which the data is read + * @param off the start offset of the data + * @param len the maximum number of bytes read + * @return the actual number of bytes read, -1 is + * returned when the end of the stream is reached. + * @exception IOException If an I/O error has occurred. + */ + public int read(byte b[], int off, int len) throws IOException { + int m, bytesMoved; + + if (len <= 0){ + return 0; + } + /* + *Fast path: len bytes already available. + */ + + if (len <= stop - rdNext){ + System.arraycopy(buff, rdNext, b, off, len); + rdNext += len; + return len; + } + /* + *General case: stream is closed or fill needs to be called + */ + bytesMoved = 0; + for(;;){ + if (rdNext != stop){ + m = Math.min(len - bytesMoved, stop - rdNext); + System.arraycopy(buff, rdNext, b, off, m); + bytesMoved += m; + rdNext += m; + if (bytesMoved == len) + return bytesMoved; + off += m; + } + if (isClosed){ + return bytesMoved; + } + fill(); + + } + } + /** + * Reads into an array of bytes. This method will + * block until some input is available. + * @param b the buffer into which the data is read + * @param off the start offset of the data + * @param len the maximum number of bytes read + * @return the actual number of bytes read, -1 is + * returned when the end of the stream is reached. + * @exception IOException If an I/O error has occurred. + */ + public void fill() throws IOException { + byte[] headerBuf = new byte[FCGIGlobalDefs.def_FCGIHeaderLen]; + int headerLen = 0; + int status = 0; + int count = 0; + for(;;) { + /* + * If buffer is empty, do a read + */ + if (rdNext == buffStop) { + try { + count = in.read(buff, 0, buffLen); + } catch (IOException e) { + setException(e); + return; + } + if (count == 0) { + setFCGIError(FCGIGlobalDefs.def_FCGIProtocolError); + return; + } + rdNext = 0; + buffStop = count; // 1 more than we read + } + /* Now buf is not empty: If the current record contains more content + * bytes, deliver all that are present in buff to callers buffer + * unless he asked for less than we have, in which case give him less + */ + if (contentLen > 0) { + count = Math.min(contentLen, buffStop - rdNext); + contentLen -= count; + if (!skip) { + stop = rdNext + count; + return; + } + else { + rdNext += count; + if (contentLen > 0) { + continue; + } + else { + skip = false; + } + } + } + /* Content has been consumed by client. + * If record was padded, skip over padding + */ + if (paddingLen > 0) { + count = Math.min(paddingLen, buffStop - rdNext); + paddingLen -= count; + rdNext += count; + if (paddingLen > 0) { + continue; // more padding to read + } + } + /* All done with current record, including the padding. + * If we are in a recursive call from Process Header, deliver EOF + */ + if (eorStop){ + stop = rdNext; + isClosed = true; + return; + } + /* + * Fill header with bytes from input buffer - get the whole header. + */ + count = Math.min(headerBuf.length - headerLen, buffStop - rdNext); + System.arraycopy(buff,rdNext, headerBuf, headerLen, count); + headerLen += count; + rdNext += count; + if (headerLen < headerBuf.length) { + continue; + } + headerLen = 0; + /* + * Interperet the header. eorStop prevents ProcessHeader from + * reading past the end of record when using stream to read content + */ + eorStop = true; + stop = rdNext; + status = 0; + status = new FCGIMessage(this).processHeader(headerBuf); + eorStop = false; + isClosed = false; + switch (status){ + case FCGIGlobalDefs.def_FCGIStreamRecord: + if (contentLen == 0) { + stop = rdNext; + isClosed = true; + return; + } + break; + case FCGIGlobalDefs.def_FCGISkip: + skip = true; + break; + case FCGIGlobalDefs.def_FCGIBeginRecord: + /* + * If this header marked the beginning of a new + * request, return role info to caller + */ + return; + case FCGIGlobalDefs.def_FCGIMgmtRecord: + break; + default: + /* + * ASSERT + */ + setFCGIError(status); + return; + + } + } + } + + /** + * Skips n bytes of input. + * @param n the number of bytes to be skipped + * @return the actual number of bytes skipped. + * @exception IOException If an I/O error has occurred. + */ + public long skip(long n) throws IOException { + byte data[] = new byte[(int)n]; + return in.read(data); + } + + /* + * An FCGI error has occurred. Save the error code in the stream + * for diagnostic purposes and set the stream state so that + * reads return EOF + */ + public void setFCGIError(int errnum) { + /* + * Preserve only the first error. + */ + if(errno == 0) { + errno = errnum; + } + isClosed = true; + } + /* + * An Exception has occurred. Save the Exception in the stream + * for diagnostic purposes and set the stream state so that + * reads return EOF + */ + public void setException(Exception errexpt) { + /* + * Preserve only the first error. + */ + if(errex == null) { + errex = errexpt; + } + isClosed = true; + } + + /* + * Clear the stream error code and end-of-file indication. + */ + public void clearFCGIError() { + errno = 0; + /* + * isClosed = false; + * XXX: should clear isClosed but work is needed to make it safe + * to do so. + */ + } + /* + * Clear the stream error code and end-of-file indication. + */ + public void clearException() { + errex = null; + /* + * isClosed = false; + * XXX: should clear isClosed but work is needed to make it safe + * to do so. + */ + } + + /* + * accessor method since var is private + */ + public int getFCGIError() { + return errno; + } + /* + * accessor method since var is private + */ + public Exception getException() { + return errex; + } + /* + * Re-initializes the stream to read data of the specified type. + */ + public void setReaderType(int streamType) { + + type = streamType; + eorStop = false; + skip = false; + contentLen = 0; + paddingLen = 0; + stop = rdNext; + isClosed = false; + } + + /* + * Close the stream. This method does not really exist for BufferedInputStream in java, + * but is implemented here for compatibility with the FCGI structures being used. It + * doent really throw any IOExceptions either, but that's there for compatiblity with + * the InputStreamInterface. + */ + public void close() throws IOException{ + isClosed = true; + stop = rdNext; + } + + /* + * Returns the number of bytes that can be read without blocking. + */ + + public int available() throws IOException { + return stop - rdNext + in.available(); + } + +} diff --git a/java/FCGIInterface.java b/java/FCGIInterface.java new file mode 100644 index 0000000..ae88650 --- /dev/null +++ b/java/FCGIInterface.java @@ -0,0 +1,248 @@ +/* + * @(#)FCGIInterface.java + * + * + * FastCGi compatibility package Interface + * + * + * Copyright (c) 1996 Open Market, Inc. + * + * See the file "LICENSE.TERMS" for information on usage and redistribution + * of this file, and for a DISCLAIMER OF ALL WARRANTIES. + * + * $Id: FCGIInterface.java,v 1.1 1999/01/31 02:45:49 roberts Exp $ + */ + +import java.net.*; +import java.io.*; +import java.util.Properties; +import FCGIGlobalDefs; +import FCGIRequest; +import FCGIInputStream; +import FCGIOutputStream; +import FCGIMessage; + +/* + * This is the FastCGI interface that the application calls to communicate with the + * FastCGI web server. This version is single threaded, and handles one request at + * a time, which is why we can have a static variable for it. + */ +public class FCGIInterface { + + /* + * Class variables + */ + public static FCGIRequest request = null; + public static boolean acceptCalled = false; + public static boolean isFCGI = true; + public static Properties startupProps; + public static ServerSocket srvSocket; + + /* + * Accepts a new request from the HTTP server and creates + * a conventional execution environment for the request. + * If the application was invoked as a FastCGI server, + * the first call to FCGIaccept indicates that the application + * has completed its initialization and is ready to accept + * a request. Subsequent calls to FCGI_accept indicate that + * the application has completed its processing of the + * current request and is ready to accept a new request. + * If the application was invoked as a CGI program, the first + * call to FCGIaccept is essentially a no-op and the second + * call returns EOF (-1) as does an error. Application should exit. + * + * If the application was invoked as a FastCGI server, + * and this is not the first call to this procedure, + * FCGIaccept first flushes any buffered output to the HTTP server. + * + * On every call, FCGIaccept accepts the new request and + * reads the FCGI_PARAMS stream into System.props. It also creates + * streams that understand FastCGI protocol and take input from + * the HTTP server send output and error output to the HTTP server, + * and assigns these new streams to System.in, System.out and + * System.err respectively. + * + * For now, we will just return an int to the caller, which is why + * this method catches, but doen't throw Exceptions. + * + */ + public int FCGIaccept() { + int acceptResult = 0; + + /* + * If first call, mark it and if fcgi save original system properties, + * If not first call, and we are cgi, we should be gone. + */ + if (!acceptCalled){ + isFCGI = System.getProperties().containsKey("FCGI_PORT"); + acceptCalled = true; + if (isFCGI) { + /* + * save original system properties (nonrequest) + * and get a server socket + */ + System.out.close(); + System.err.close(); + startupProps = new Properties(System.getProperties()); + String str = + new String(System.getProperty("FCGI_PORT")); + if (str.length() <= 0) { + return -1; + } + int portNum = Integer.parseInt(str); + + try { + srvSocket = new ServerSocket(portNum); + } catch (IOException e) { + request.socket = null; + srvSocket = null; + request = null; + return -1; + } + } + } + else { + if (!isFCGI){ + return -1; + } + } + /* + * If we are cgi, just leave everything as is, otherwise set up env + */ + if (isFCGI){ + try { + acceptResult = FCGIAccept(); + } catch (IOException e) { + return -1; + } + if (acceptResult < 0){ + return -1; + } + + /* + * redirect stdin, stdout and stderr to fcgi socket + */ + System.setIn(new BufferedInputStream(request.inStream, 8192)); + System.setOut(new PrintStream(new BufferedOutputStream( + request.outStream, 8192))); + System.setErr(new PrintStream(new BufferedOutputStream( + request.errStream, 512))); + System.setProperties(request.params); + } + return 0; + } + + /* + * Accepts a new request from the HTTP server. + * Finishes the request accepted by the previous call + * to FCGI_Accept. Sets up the FCGI environment and reads + * saved and per request environmental varaibles into + * the request object. (This is redundant on System.props + * as long as we can handle only one request object.) + */ + int FCGIAccept() throws IOException{ + + boolean isNewConnection; + boolean errCloseEx = false; + boolean outCloseEx = false; + + if (request != null) { + /* + * Complete the previous request + */ + System.err.close(); + System.out.close(); + boolean prevRequestfailed = (errCloseEx || outCloseEx || + request.inStream.getFCGIError() != 0 || + request.inStream.getException() != null); + if (prevRequestfailed || !request.keepConnection ) { + request.socket.close(); + request.socket = null; + } + if (prevRequestfailed) { + request = null; + return -1; + } + } + else { + /* + * Get a Request and initialize some variables + */ + request = new FCGIRequest(); + request.socket = null; + request.inStream = null; + } + isNewConnection = false; + + /* + * if connection isnt open accept a new connection (blocking) + */ + for(;;) { + if (request.socket == null){ + try { + request.socket = srvSocket.accept(); + } catch (IOException e) { + request.socket = null; + request = null; + return -1; + } + isNewConnection = true; + } + + /* Try reading from new connection. If the read fails and + * it was an old connection the web server probably closed it; + * try making a new connection before giving up + */ + request.isBeginProcessed = false; + request.inStream = + new FCGIInputStream((FileInputStream)request. + socket.getInputStream(), + 8192, 0, request); + request.inStream.fill(); + if (request.isBeginProcessed) { + break; + } + request.socket.close(); + + request.socket = null; + if (isNewConnection) { + return -1; + } + } + /* + * Set up the objects for the new request + */ + request.params = new Properties(startupProps); + switch(request.role) { + case FCGIGlobalDefs.def_FCGIResponder: + request.params.put("ROLE","RESPONDER"); + break; + case FCGIGlobalDefs.def_FCGIAuthorizer: + request.params.put("ROLE", "AUTHORIZER"); + break; + case FCGIGlobalDefs.def_FCGIFilter: + request.params.put("ROLE", "FILTER"); + break; + default: + return -1; + } + request.inStream.setReaderType(FCGIGlobalDefs.def_FCGIParams); + /* + * read the rest of request parameters + */ + if (new FCGIMessage(request.inStream).readParams(request.params) < 0) { + return -1; + } + request.inStream.setReaderType(FCGIGlobalDefs.def_FCGIStdin); + request.outStream + = new FCGIOutputStream((FileOutputStream)request.socket. + getOutputStream(), 8192, + FCGIGlobalDefs.def_FCGIStdout,request); + request.errStream + = new FCGIOutputStream((FileOutputStream)request.socket. + getOutputStream(), 512, + FCGIGlobalDefs.def_FCGIStderr,request); + request.numWriters = 2; + return 0; + } +} diff --git a/java/FCGIMessage.java b/java/FCGIMessage.java new file mode 100644 index 0000000..e0a710b --- /dev/null +++ b/java/FCGIMessage.java @@ -0,0 +1,403 @@ +/* + * @(#)FCGIMessage.java + * + * + * FastCGi compatibility package Interface + * + * + * Copyright (c) 1996 Open Market, Inc. + * + * See the file "LICENSE.TERMS" for information on usage and redistribution + * of this file, and for a DISCLAIMER OF ALL WARRANTIES. + * + * $Id: FCGIMessage.java,v 1.1 1999/01/31 02:45:52 roberts Exp $ + */ + +import java.io.*; +import java.util.Properties; + +/* This class handles reading and building the fastcgi messages. + * For reading incoming mesages, we pass the input + * stream as a param to the constructor rather than to each method. + * Methods that build messages use and return internal buffers, so they + * dont need a stream. + */ + +public class FCGIMessage { + + /* + * Instance variables + */ + /* + * FCGI Message Records + * The logical structures of the FCGI Message Records. + * Fields are originally 1 unsigned byte in message + * unless otherwise noted. + */ + /* + * FCGI Header + */ + private int h_version; + private int h_type; + private int h_requestID; // 2 bytes + private int h_contentLength; // 2 bytes + private int h_paddingLength; + /* + * FCGI BeginRequest body. + */ + private int br_role; // 2 bytes + private int br_flags; + + private FCGIInputStream in; + + /* + * constructor - Java would do this implicitly. + */ + public FCGIMessage(){ + super(); + } + /* + * constructor - get the stream. + */ + public FCGIMessage(FCGIInputStream instream){ + in = instream; + } + + /* + * Message Reading Methods + */ + + /* + * Interpret the FCGI Message Header. Processes FCGI + * BeginRequest and Management messages. Param hdr is the header. + * The calling routine has to keep track of the stream reading + * management or use FCGIInputStream.fill() which does just that. + */ + public int processHeader(byte[] hdr) throws IOException{ + processHeaderBytes(hdr); + if (h_version != FCGIGlobalDefs.def_FCGIVersion1) { + return(FCGIGlobalDefs.def_FCGIUnsupportedVersion); + } + in.contentLen = h_contentLength; + in.paddingLen = h_paddingLength; + if (h_type == FCGIGlobalDefs.def_FCGIBeginRequest) { + return processBeginRecord(h_requestID); + } + if (h_requestID == FCGIGlobalDefs.def_FCGINullRequestID) { + return processManagementRecord(h_type); + } + if (h_requestID != in.request.requestID) { + return(FCGIGlobalDefs.def_FCGISkip); + } + if (h_type != in.type) { + return(FCGIGlobalDefs.def_FCGIProtocolError); + } + return(FCGIGlobalDefs.def_FCGIStreamRecord); + } + + /* Put the unsigned bytes in the incoming FCGI header into + * integer form for Java, concatinating bytes when needed. + * Because Java has no unsigned byte type, we have to be careful + * about signed numeric promotion to int. + */ + private void processHeaderBytes(byte[] hdrBuf){ + h_version = hdrBuf[0] & 0xFF; + h_type = hdrBuf[1] & 0xFF; + h_requestID = ((hdrBuf[2] & 0xFF) << 8) | (hdrBuf[3] & 0xFF); + h_contentLength = ((hdrBuf[4] & 0xFF) << 8) | (hdrBuf[5] & 0xFF); + h_paddingLength = hdrBuf[6] & 0xFF; + } + + /* + * Reads FCGI Begin Request Record. + */ + public int processBeginRecord(int requestID) throws IOException { + byte beginReqBody[]; + byte endReqMsg[]; + if (requestID == 0 || in.contentLen + != FCGIGlobalDefs.def_FCGIEndReqBodyLen) { + return FCGIGlobalDefs.def_FCGIProtocolError; + } + /* + * If the webserver is multiplexing the connection, + * this library can't deal with it, so repond with + * FCGIEndReq message with protocolStatus FCGICantMpxConn + */ + if (in.request.isBeginProcessed) { + endReqMsg = new byte[FCGIGlobalDefs.def_FCGIHeaderLen + + FCGIGlobalDefs.def_FCGIEndReqBodyLen]; + System.arraycopy(makeHeader( + FCGIGlobalDefs.def_FCGIEndRequest, + requestID, + FCGIGlobalDefs.def_FCGIEndReqBodyLen, + 0), 0, endReqMsg, 0, + FCGIGlobalDefs.def_FCGIHeaderLen); + System.arraycopy(makeEndrequestBody(0, + FCGIGlobalDefs.def_FCGICantMpxConn), 0, + endReqMsg, + FCGIGlobalDefs.def_FCGIHeaderLen, + FCGIGlobalDefs.def_FCGIEndReqBodyLen); + /* + * since isBeginProcessed is first set below,this + * can't be out first call, so request.out is properly set + */ + try { + in.request.outStream.write(endReqMsg, 0, + FCGIGlobalDefs.def_FCGIHeaderLen + + FCGIGlobalDefs.def_FCGIEndReqBodyLen); + } catch (IOException e){ + in.request.outStream.setException(e); + return -1; + } + } + /* + * Accept this new request. Read the record body + */ + in.request.requestID = requestID; + beginReqBody = + new byte[FCGIGlobalDefs.def_FCGIBeginReqBodyLen]; + if (in.read(beginReqBody, 0, + FCGIGlobalDefs.def_FCGIBeginReqBodyLen) != + FCGIGlobalDefs.def_FCGIBeginReqBodyLen) { + return FCGIGlobalDefs.def_FCGIProtocolError; + } + br_flags = beginReqBody[2] & 0xFF; + in.request.keepConnection + = (br_flags & FCGIGlobalDefs.def_FCGIKeepConn) != 0; + br_role = ((beginReqBody[0] & 0xFF) << 8) | (beginReqBody[1] & 0xFF); + in.request.role = br_role; + in.request.isBeginProcessed = true; + return FCGIGlobalDefs.def_FCGIBeginRecord; + } + + /* + * Reads and Responds to a Management Message. The only type of + * management message this library understands is FCGIGetValues. + * The only variables that this library's FCGIGetValues understands + * are def_FCGIMaxConns, def_FCGIMaxReqs, and def_FCGIMpxsConns. + * Ignore the other management variables, and repsond to other + * management messages with FCGIUnknownType. + */ + public int processManagementRecord(int type) throws IOException { + + byte[] response = new byte[64]; + int wrndx = response[FCGIGlobalDefs.def_FCGIHeaderLen]; + int value, len, plen; + if (type == FCGIGlobalDefs.def_FCGIGetValues) { + Properties tmpProps = new Properties(); + readParams(tmpProps); + + if (in.getFCGIError() != 0 || in.contentLen != 0) { + return FCGIGlobalDefs.def_FCGIProtocolError; + } + if (tmpProps.containsKey( + FCGIGlobalDefs.def_FCGIMaxConns)) { + makeNameVal( + FCGIGlobalDefs.def_FCGIMaxConns, "1", + response, wrndx); + } + else { + if (tmpProps.containsKey( + FCGIGlobalDefs.def_FCGIMaxReqs)) { + makeNameVal( + FCGIGlobalDefs.def_FCGIMaxReqs, "1", + response, wrndx); + } + else { + if (tmpProps.containsKey( + FCGIGlobalDefs.def_FCGIMaxConns)) { + makeNameVal( + FCGIGlobalDefs.def_FCGIMpxsConns, "0", + response, wrndx); + } + } + } + plen = 64 - wrndx; + len = wrndx - FCGIGlobalDefs.def_FCGIHeaderLen; + System.arraycopy(makeHeader( + FCGIGlobalDefs.def_FCGIGetValuesResult, + FCGIGlobalDefs.def_FCGINullRequestID, + len, plen), 0, + response, 0, + FCGIGlobalDefs.def_FCGIHeaderLen); + } + else { + plen = len = + FCGIGlobalDefs.def_FCGIUnknownBodyTypeBodyLen; + System.arraycopy(makeHeader( + FCGIGlobalDefs.def_FCGIUnknownType, + FCGIGlobalDefs.def_FCGINullRequestID, + len, 0), 0, + response, 0, + FCGIGlobalDefs.def_FCGIHeaderLen); + System.arraycopy(makeUnknownTypeBodyBody(h_type), 0, + response, + FCGIGlobalDefs.def_FCGIHeaderLen, + FCGIGlobalDefs.def_FCGIUnknownBodyTypeBodyLen); + } + /* + * No guarantee that we have a request yet, so + * dont use fcgi output stream to reference socket, instead + * use the FileInputStream that refrences it. Also + * nowhere to save exception, since this is not FCGI stream. + */ + + try { + in.request.socket.getOutputStream().write(response, 0, + FCGIGlobalDefs.def_FCGIHeaderLen + + FCGIGlobalDefs.def_FCGIUnknownBodyTypeBodyLen); + + } catch (IOException e){ + return -1; + } + return FCGIGlobalDefs.def_FCGIMgmtRecord; + } + + /* + * Makes a name/value with name = string of some length, and + * value a 1 byte integer. Pretty specific to what we are doing + * above. + */ + void makeNameVal(String name, String value, byte[] dest, int pos) { + int nameLen = name.length(); + if (nameLen < 0x80) { + dest[pos++] = (byte)nameLen; + }else { + dest[pos++] = (byte)(((nameLen >> 24) | 0x80) & 0xff); + dest[pos++] = (byte)((nameLen >> 16) & 0xff); + dest[pos++] = (byte)((nameLen >> 8) & 0xff); + dest[pos++] = (byte)nameLen; + } + int valLen = value.length(); + if (valLen < 0x80) { + dest[pos++] = (byte)valLen; + }else { + dest[pos++] = (byte)(((valLen >> 24) | 0x80) & 0xff); + dest[pos++] = (byte)((valLen >> 16) & 0xff); + dest[pos++] = (byte)((valLen >> 8) & 0xff); + dest[pos++] = (byte)valLen; + } + name.getBytes(0, nameLen, dest, pos); + pos += nameLen; + value.getBytes(0, valLen, dest, pos); + pos += valLen; + } + + /* + * Read FCGI name-value pairs from a stream until EOF. Put them + * into a Properties object, storing both as strings. + */ + public int readParams(Properties props) throws IOException{ + int nameLen, valueLen; + byte lenBuff[] = new byte[3]; + int i = 1; + + while ((nameLen = in.read()) != -1) { + i++; + if ((nameLen & 0x80) != 0) { + if ((in.read( lenBuff, 0, 3)) != 3) { + in.setFCGIError( + FCGIGlobalDefs.def_FCGIParamsError); + return -1; + } + nameLen = ((nameLen & 0x7f) << 24) + | ((lenBuff[0] & 0xFF) << 16) + | ((lenBuff[1] & 0xFF) << 8) + | (lenBuff[2] & 0xFF); + } + + if ((valueLen = in.read()) == -1) { + in.setFCGIError( + FCGIGlobalDefs.def_FCGIParamsError); + return -1; + } + if ((valueLen & 0x80) != 0) { + if ((in.read( lenBuff, 0, 3)) != 3) { + in.setFCGIError( + FCGIGlobalDefs.def_FCGIParamsError); + return -1; + } + valueLen = ((valueLen & 0x7f) << 24) + | ((lenBuff[0] & 0xFF) << 16) + | ((lenBuff[1] & 0xFF) << 8) + | (lenBuff[2] & 0xFF); + } + + /* + * nameLen and valueLen are now valid; read the name + * and the value from the stream and construct a standard + * environmental entity + */ + byte[] name = new byte[nameLen]; + byte[] value = new byte[valueLen]; + if (in.read(name ,0, nameLen) != nameLen) { + in.setFCGIError( + FCGIGlobalDefs.def_FCGIParamsError); + return -1; + } + + if(in.read(value, 0, valueLen) != valueLen) { + in.setFCGIError( + FCGIGlobalDefs.def_FCGIParamsError); + return -1; + } + String strName = new String(name, 0, 0, name.length); + String strValue = new String(value, 0, 0, value.length); + props.put(strName, strValue); + } + return 0; + + + } + /* + * Message Building Methods + */ + + /* + * Build an FCGI Message Header - + */ + public byte[] makeHeader(int type, + int requestId, + int contentLength, + int paddingLength) { + byte[] header = new byte[FCGIGlobalDefs.def_FCGIHeaderLen]; + header[0] = (byte)FCGIGlobalDefs.def_FCGIVersion1; + header[1] = (byte)type; + header[2] = (byte)((requestId >> 8) & 0xff); + header[3] = (byte)((requestId ) & 0xff); + header[4] = (byte)((contentLength >> 8) & 0xff); + header[5] = (byte)((contentLength ) & 0xff); + header[6] = (byte)paddingLength; + header[7] = 0; //reserved byte + return header; + } + /* + * Build an FCGI Message End Request Body + */ + public byte[] makeEndrequestBody(int appStatus,int protocolStatus){ + byte body[] = new byte[FCGIGlobalDefs.def_FCGIEndReqBodyLen]; + body[0] = (byte)((appStatus >> 24) & 0xff); + body[1] = (byte)((appStatus >> 16) & 0xff); + body[2] = (byte)((appStatus >> 8) & 0xff); + body[3] = (byte)((appStatus ) & 0xff); + body[4] = (byte)protocolStatus; + for (int i = 5; i < 8; i++) { + body[i] = 0; + } + return body; + } + /* + * Build an FCGI Message UnknownTypeBodyBody + */ + public byte[] makeUnknownTypeBodyBody(int type){ + byte body[] = + new byte[FCGIGlobalDefs.def_FCGIUnknownBodyTypeBodyLen]; + body[0] = (byte)type; + for (int i = 1; + i < FCGIGlobalDefs.def_FCGIUnknownBodyTypeBodyLen; i++) { + body[i] = 0; + } + return body; + } + +} //end class diff --git a/java/FCGIOutputStream.java b/java/FCGIOutputStream.java new file mode 100644 index 0000000..32fdd40 --- /dev/null +++ b/java/FCGIOutputStream.java @@ -0,0 +1,335 @@ +/* + * @(#)FCGIOutputStream.java + * + * FastCGi compatibility package Interface + * + * Copyright (c) 1996 Open Market, Inc. + * + * See the file "LICENSE.TERMS" for information on usage and redistribution + * of this file, and for a DISCLAIMER OF ALL WARRANTIES. + * + * $Id: FCGIOutputStream.java,v 1.1 1999/01/31 02:45:53 roberts Exp $ + */ + +import java.io.*; +import FCGIRequest; +import FCGIGlobalDefs; +import FCGIMessage; +import FCGIInputStream; + +/** + * This stream understands FCGI prototcol. + */ + +public class FCGIOutputStream extends OutputStream { + + /* Stream vars */ + + public int wrNext; + public int stop; + public boolean isClosed; + + /* require methods to set, get and clear */ + private int errno; + private Exception errex; + + /* data vars */ + + public byte buff[]; + public int buffLen; + public int buffStop; + public int type; + public boolean isAnythingWritten; + public boolean rawWrite; + public FCGIRequest request; + + public FileOutputStream out; + + /** + * Creates a new output stream to manage fcgi prototcol stuff + * @param out the output stream buflen length of buffer streamType + */ + public FCGIOutputStream(FileOutputStream outStream, + int bufLen, int streamType, + FCGIRequest inreq) { + out = outStream; + buffLen = Math.min(bufLen, FCGIGlobalDefs.def_FCGIMaxLen); + buff = new byte[buffLen]; + type = streamType; + stop = buffStop = buffLen; + isAnythingWritten = false; + rawWrite = false; + wrNext = FCGIGlobalDefs.def_FCGIHeaderLen; + isClosed = false; + request = inreq; + } + + /** + * Writes a byte to the output stream. + */ + public void write(int c) throws IOException { + if(wrNext != stop) { + buff[wrNext++] = (byte)c; + return; + } + if(isClosed) { + throw new EOFException(); + } + empty(false); + if(wrNext != stop) { + buff[wrNext++] = (byte)c; + return; + } + /* NOTE: ASSERT(stream->isClosed); */ + /* bug in emptyBuffProc if not */ + throw new EOFException(); + } + + /** + * Writes an array of bytes. This method will block until the bytes + * are actually written. + * @param b the data to be written + */ + public void write(byte b[]) throws IOException{ + write(b, 0, b.length); + } + + /** + * Writes len consecutive bytes from off in the array b + * into the output stream. Performs no interpretation + * of the output bytes. Making the user convert the string to + * bytes is in line with current Java practice. + */ + public void write(byte b[], int off, int len) throws IOException { + int m, bytesMoved; + /* + * Fast path: room for n bytes in the buffer + */ + if(len <= (stop - wrNext)) { + System.arraycopy(b, off, buff, wrNext, len); + wrNext += len; + return; + } + /* + * General case: stream is closed or buffer empty procedure + * needs to be called + */ + bytesMoved = 0; + for (;;) { + if(wrNext != stop) { + m = Math.min(len - bytesMoved, stop - wrNext); + System.arraycopy(b, off, buff, wrNext, m); + bytesMoved += m; + wrNext += m; + if(bytesMoved == len) { + return; + } + off += m; + } + if(isClosed) { + throw new EOFException(); + } + empty(false); + } + } + + /** + * Encapsulates any buffered stream content in a FastCGI + * record. If !doClose, writes the data, making the buffer + * empty. + */ + public void empty(boolean doClose) throws IOException { + int cLen; + /* + * Alignment padding omitted in Java + */ + if (!rawWrite) { + cLen = wrNext - FCGIGlobalDefs.def_FCGIHeaderLen; + if(cLen > 0) { + System.arraycopy(new FCGIMessage().makeHeader(type, + request.requestID, cLen, 0), + 0, buff, 0, + FCGIGlobalDefs.def_FCGIHeaderLen); + } + else { + wrNext = 0; + } + } + if (doClose) { + writeCloseRecords(); + } + if (wrNext != 0) { + isAnythingWritten = true; + try { + out.write(buff, 0, wrNext); + } catch (IOException e) { + setException(e); + return; + } + wrNext = 0; + } + /* + * The buffer is empty. + */ + if(!rawWrite) { + wrNext += FCGIGlobalDefs.def_FCGIHeaderLen; + } + } + + /** + * Close the stream. + */ + public void close() throws IOException { + if (isClosed) { + return; + } + empty(true); + /* + * if isClosed, will return with EOFException from write. + */ + isClosed = true; + stop = wrNext; + return; + } + + /** + * Flushes any buffered output. + * Server-push is a legitimate application of flush. + * Otherwise, it is not very useful, since FCGIAccept + * does it implicitly. flush may reduce performance + * by increasing the total number of operating system calls + * the application makes. + */ + public void flush() throws IOException { + if (isClosed) { + return; + } + empty(false); + /* + * if isClosed, will return with EOFException from write. + */ + return; + } + + /** + * An FCGI error has occurred. Save the error code in the stream + * for diagnostic purposes and set the stream state so that + * reads return EOF + */ + public void setFCGIError(int errnum) { + /* + * Preserve only the first error. + */ + if (errno == 0) { + errno = errnum; + } + isClosed = true; + } + + /** + * An Exception has occurred. Save the Exception in the stream + * for diagnostic purposes and set the stream state so that + * reads return EOF + */ + public void setException(Exception errexpt) { + /* + * Preserve only the first error. + */ + if (errex == null) { + errex = errexpt; + } + isClosed = true; + } + + /** + * Clear the stream error code and end-of-file indication. + */ + public void clearFCGIError() { + errno = 0; + /* + * isClosed = false; + * XXX: should clear isClosed but work is needed to make it safe + * to do so. + */ + } + + /** + * Clear the stream error code and end-of-file indication. + */ + public void clearException() { + errex = null; + /* + * isClosed = false; + * XXX: should clear isClosed but work is needed to make it safe + * to do so. + */ + } + + /** + * accessor method since var is private + */ + public int etFCGIError() { + return errno; + } + + /** + * accessor method since var is private + */ + public Exception getException() { + return errex; + } + + /** + * Writes an EOF record for the stream content if necessary. + * If this is the last writer to close, writes an FCGI_END_REQUEST + * record. + */ + public void writeCloseRecords() throws IOException { + FCGIMessage msg = new FCGIMessage(); + /* + * Enter rawWrite mode so final records won't be + * encapsulated as + * stream data. + */ + rawWrite = true; + /* + * Generate EOF for stream content if needed. + */ + if(!(type == FCGIGlobalDefs.def_FCGIStderr + && wrNext == 0 + && !isAnythingWritten)) { + byte hdr[] = + new byte[FCGIGlobalDefs.def_FCGIHeaderLen]; + System.arraycopy(msg.makeHeader(type, + request.requestID, + 0, 0), + 0, hdr,0, + FCGIGlobalDefs.def_FCGIHeaderLen); + write(hdr, 0, hdr.length); + } + /* + * Generate FCGI_END_REQUEST record if needed. + */ + if(request.numWriters == 1) { + byte endReq[] = + new byte[FCGIGlobalDefs.def_FCGIHeaderLen + + FCGIGlobalDefs.def_FCGIEndReqBodyLen]; + System.arraycopy(msg.makeHeader( + FCGIGlobalDefs.def_FCGIEndRequest, + request.requestID, + FCGIGlobalDefs.def_FCGIEndReqBodyLen,0), + 0, endReq, 0, + FCGIGlobalDefs.def_FCGIHeaderLen); + System.arraycopy(msg.makeEndrequestBody( + request.appStatus, + FCGIGlobalDefs.def_FCGIRequestComplete), + 0,endReq, + FCGIGlobalDefs.def_FCGIHeaderLen, + FCGIGlobalDefs.def_FCGIEndReqBodyLen); + write(endReq,0, FCGIGlobalDefs.def_FCGIHeaderLen + + FCGIGlobalDefs.def_FCGIEndReqBodyLen); + } + request.numWriters--; + } +} + diff --git a/java/FCGIRequest.java b/java/FCGIRequest.java new file mode 100644 index 0000000..d1d44b9 --- /dev/null +++ b/java/FCGIRequest.java @@ -0,0 +1,51 @@ +/* + * @(#)FCGIRequest.java + * + * FastCGi compatibility package Interface + * + * Copyright (c) 1996 Open Market, Inc. + * + * See the file "LICENSE.TERMS" for information on usage and redistribution + * of this file, and for a DISCLAIMER OF ALL WARRANTIES. + * + * $Id: FCGIRequest.java,v 1.1 1999/01/31 02:45:55 roberts Exp $ + */ + +import java.net.*; +import java.io.FileDescriptor; +import java.util.Properties; +import FCGIInputStream; +import FCGIOutputStream; +public class FCGIRequest { + + /* This class has no methods. Right now we are single threaded + * so there is only one request object at any given time which + * is refrenced by an FCGIInterface class variable . All of this + * object's data could just as easily be declared directly there. + * When we thread, this will change, so we might as well use a + * seperate class. In line with this thinking, though somewhat + * more perversely, we kept the socket here. + */ + /* + * class variables + */ + /*public static Socket socket; */ + // same for all requests + + /* + * instance variables + */ + public Socket socket; + public boolean isBeginProcessed; + public int requestID; + public boolean keepConnection; + public int role; + public int appStatus; + public int numWriters; + public FCGIInputStream inStream; + public FCGIOutputStream outStream; + public FCGIOutputStream errStream; + public Properties params; +} + +