/*
* Copyright (c) 1998-2011 Caucho Technology -- all rights reserved
*
* This file is part of Resin(R) Open Source
*
* Each copy or derived work must preserve the copyright notice and this
* notice unmodified.
*
* Resin Open Source 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 2 of the License, or
* (at your option) any later version.
*
* Resin Open Source 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, or any warranty
* of NON-INFRINGEMENT. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License
* along with Resin Open Source; if not, write to the
*
* Free Software Foundation, Inc.
* 59 Temple Place, Suite 330
* Boston, MA 02111-1307 USA
*
* @author Scott Ferguson
*/
package com.caucho.server.fastcgi;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Locale;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.caucho.network.listen.ProtocolConnection;
import com.caucho.network.listen.SocketLink;
import com.caucho.server.cluster.Server;
import com.caucho.server.dispatch.BadRequestException;
import com.caucho.server.dispatch.Invocation;
import com.caucho.server.dispatch.InvocationDecoder;
import com.caucho.server.dispatch.InvocationServer;
import com.caucho.server.http.AbstractHttpRequest;
import com.caucho.server.http.HttpBufferStore;
import com.caucho.server.http.InvocationKey;
import com.caucho.server.webapp.WebApp;
import com.caucho.util.CharBuffer;
import com.caucho.util.CharSegment;
import com.caucho.vfs.ClientDisconnectException;
import com.caucho.vfs.ReadStream;
import com.caucho.vfs.StreamImpl;
import com.caucho.vfs.WriteStream;
/**
* Handles a new request from a FastCGI connection.
*/
public class FastCgiRequest extends AbstractHttpRequest
implements ProtocolConnection
{
private static final Logger log
= Logger.getLogger(FastCgiRequest.class.getName());
private static final int FCGI_HEADER_LEN = 8;
private static final int FCGI_VERSION_1 = 1;
private static final int FCGI_BEGIN_REQUEST = 1;
private static final int FCGI_ABORT_REQUEST = 2;
private static final int FCGI_END_REQUEST = 3;
private static final int FCGI_PARAMS = 4;
private static final int FCGI_STDIN = 5;
private static final int FCGI_STDOUT = 6;
private static final int FCGI_STDERR = 7;
private static final int FCGI_DATA = 8;
private static final int FCGI_GET_VALUES = 9;
private static final int FCGI_GET_VALUES_RESULT = 10;
private static final int FCGI_UNKNOWN_TYPE = 11;
private static final int FCGI_MAXTYPE = FCGI_UNKNOWN_TYPE;
private static final int FCGI_NULL_REQUEST_ID = 0;
private static final int FCGI_KEEP_CONN = 1;
private static final int FCGI_RESPONDER = 1;
private static final int FCGI_AUTHORIZER = 2;
private static final int FCGI_FILTER = 3;
// protocolStatus for FCGI_EndRequestBody
private static final int FCGI_REQUEST_COMPLETE = 0;
private static final int FCGI_CANT_MPX_CONN = 1;
private static final int FCGI_OVERLOADED = 2;
private static final int FCGI_UNKNOWN_ROLE = 3;
private static final int HTTP_1_1 = 0x11;
private static final int LEN_CONTENT_LENGTH = 14;
private static final int HU_CONTENT_LENGTH = (14 << 8) | 'C';
private static final int HL_CONTENT_LENGTH = (14 << 8) | 'c';
private static final int LEN_CONTENT_TYPE = 12;
private static final byte []REQUEST_METHOD = "REQUEST_METHOD".getBytes();
private static final int HU_REQUEST_METHOD = (14 << 8) | 'R';
private static final int HL_REQUEST_METHOD = (14 << 8) | 'r';
private static final byte []REQUEST_URI = "REQUEST_URI".getBytes();
private static final int HU_REQUEST_URI = (11 << 8) | 'R';
private static final int HL_REQUEST_URI = (11 << 8) | 'r';
private static final byte []SERVER_PROTOCOL = "SERVER_PROTOCOL".getBytes();
private static final int HU_SERVER_PROTOCOL = (15 << 8) | 'S';
private static final int HL_SERVER_PROTOCOL = (15 << 8) | 's';
private static final int LEN_SCRIPT_NAME = 11;
private CharBuffer _method; // "GET"
private String _methodString;
private CharBuffer _uriHost; // www.caucho.com:8080
private CharSequence _host;
private CharBuffer _hostBuffer = new CharBuffer();
private CharBuffer _queryString = new CharBuffer();
private byte []_uri; // "/path/test.jsp/Junk?query=7"
private int _uriLength;
private int _urlLengthMax = 8192;
private byte []_keyBuffer = new byte[256];
private CharBuffer _protocol; // "HTTP/1.0"
private int _version;
private final InvocationKey _invocationKey = new InvocationKey();
private char []_headerBuffer;
private int _headerOffset;
private boolean _isSecure;
private CharSegment []_headerKeys;
private CharSegment []_headerValues;
private int _headerSize;
private boolean _hasRequest;
/*
private ChunkedInputStream _chunkedInputStream = new ChunkedInputStream();
private ContentLengthStream _contentLengthStream = new ContentLengthStream();
*/
// write stream from the connection
private WriteStream _rawWrite;
// servlet write stream
private WriteStream _writeStream;
private ServletFilter _filter = new ServletFilter(this);
private boolean _initAttributes;
private byte []_buffer = new byte[16];
/**
* Creates a new HttpRequest. New connections reuse the request.
*
* @param server the owning server.
*/
public FastCgiRequest(Server server, SocketLink conn)
{
super(server, conn);
_method = new CharBuffer();
_uriHost = new CharBuffer();
_protocol = new CharBuffer();
_rawWrite = conn.getWriteStream();
getWriteStream();
}
@Override
public FastCgiResponse createResponse()
{
return new FastCgiResponse(this, getWriteStream());
}
WriteStream getWriteStream()
{
if (_writeStream == null) {
_writeStream = new WriteStream();
_writeStream.setReuseBuffer(true);
}
return _writeStream;
}
/**
* Return true if the request waits for a read before beginning.
*/
public final boolean isWaitForRead()
{
return true;
}
/**
* Returns true if the request exists
*/
@Override
public boolean hasRequest()
{
return _hasRequest;
}
/**
* Handles a new HTTP request.
*
* <p>Note: ClientDisconnectException must be rethrown to
* the caller.
*
* @return true if the connection should stay open (keepalive)
*/
public boolean handleRequest()
throws IOException
{
_hasRequest = false;
SocketLink conn = getConnection();
Server server = getServer();
Thread thread = Thread.currentThread();
ClassLoader oldLoader = thread.getContextClassLoader();
try {
thread.setContextClassLoader(server.getClassLoader());
HttpBufferStore httpBuffer = server.allocateHttpBuffer();
startRequest(httpBuffer);
startInvocation();
ReadStream is = getRawRead();
WriteStream os = getRawWrite();
_filter.init(is, os);
_writeStream.init(_filter);
// _writeStream.setWritePrefix(3);
try {
_hasRequest = false;
while (readPacket(is)) {
}
if (! _hasRequest) {
if (log.isLoggable(Level.FINE))
log.fine(dbgId() + "read timeout");
return false;
}
startInvocation();
_isSecure = conn.isSecure() || conn.getLocalPort() == 443;
/*
if (_protocol.length() == 0)
_protocol.append("HTTP/0.9");
if (log.isLoggable(Level.FINE)) {
log.fine(dbgId() + _method + " " +
new String(_uri, 0, _uriLength) + " " + _protocol);
log.fine(dbgId() + "Remote-IP: " + _conn.getRemoteHost() + ":" + _conn.getRemotePort());
}
parseHeaders(_rawRead);
if (getVersion() >= HTTP_1_1 && isForce10()) {
_protocol.clear();
_protocol.append("HTTP/1.0");
_version = HTTP_1_0;
}
*/
} catch (ClientDisconnectException e) {
throw e;
} catch (Throwable e) {
log.log(Level.FINER, e.toString(), e);
throw new BadRequestException(String.valueOf(e), e);
}
CharSequence host = getHost();
String ipHost = conn.getVirtualHost();
if (ipHost != null)
host = ipHost;
_invocationKey.init(_isSecure,
host, conn.getLocalPort(),
_uri, _uriLength);
Invocation invocation = getInvocation(host);
if (invocation == null)
return false;
getRequestFacade().setInvocation(invocation);
startInvocation();
invocation.service(getRequestFacade(), getResponseFacade());
} catch (ClientDisconnectException e) {
// XXX: _response.killCache();
throw e;
} catch (Throwable e) {
log.log(Level.FINE, e.toString(), e);
// XXX: _response.killCache();
killKeepalive("request exception: " + e);
/*
try {
getErrorManager().sendServletError(e, this, _response);
} catch (ClientDisconnectException e1) {
throw e1;
} catch (Throwable e1) {
log.log(Level.FINE, e1.toString(), e1);
}
*/
WebApp webApp = server.getDefaultWebApp();
if (webApp != null)
webApp.accessLog(getRequestFacade(), getResponseFacade());
return false;
} finally {
finishInvocation();
if (! isSuspend()) {
finishRequest();
}
thread.setContextClassLoader(oldLoader);
}
if (log.isLoggable(Level.FINE)) {
log.fine(dbgId() +
(isKeepalive() ? "keepalive" : "no-keepalive"));
}
return isKeepalive();
}
private Invocation getInvocation(CharSequence host)
throws Throwable
{
InvocationServer server = getServer().getInvocationServer();
Invocation invocation = server.getInvocation(_invocationKey);
if (invocation == null) {
invocation = server.createInvocation();
invocation.setSecure(_isSecure);
if (host != null) {
String hostName = host.toString().toLowerCase(Locale.ENGLISH);
invocation.setHost(hostName);
invocation.setPort(getConnection().getLocalPort());
// Default host name if the host doesn't have a canonical
// name
int p = hostName.indexOf(':');
if (p > 0)
invocation.setHostName(hostName.substring(0, p));
else
invocation.setHostName(hostName);
}
InvocationDecoder decoder = server.getInvocationDecoder();
decoder.splitQueryAndUnescape(invocation, _uri, _uriLength);
/* XXX: common to AbstractHttpRequest
if (_server.isModified()) {
_server.logModified(log);
_invocation = invocation;
if (_server instanceof Server)
_invocation.setWebApp(((Server) _server).getDefaultWebApp());
restartServer();
return null;
}
*/
invocation = server.buildInvocation(_invocationKey.clone(),
invocation);
}
invocation = invocation.getRequestInvocation(getRequestFacade());
return invocation;
}
public int getVersion()
{
return HTTP_1_1;
}
/**
* Returns the byte buffer containing the request URI
*/
public byte []getUriBuffer()
{
return _uri;
}
/**
* Returns the length of the request URI
*/
public int getUriLength()
{
return _uriLength;
}
/**
* Returns the header.
*/
public String getMethod()
{
if (_methodString == null) {
CharSegment cb = getMethodBuffer();
if (cb.length() == 0) {
_methodString = "GET";
return _methodString;
}
/*
switch (cb.charAt(0)) {
case 'G':
_methodString = cb.equals(_getCb) ? "GET" : cb.toString();
break;
case 'H':
_methodString = cb.equals(_headCb) ? "HEAD" : cb.toString();
break;
case 'P':
_methodString = cb.equals(_postCb) ? "POST" : cb.toString();
break;
default:
_methodString = cb.toString();
}
*/
_methodString = cb.toString();
}
return _methodString;
}
/**
* Returns the protocol.
*/
public String getProtocol()
{
if (_protocol.getLength() > 0)
return _protocol.toString();
else
return "HTTP/1.1";
}
/**
* Returns a buffer containing the request method.
*/
public CharSegment getMethodBuffer()
{
return _method;
}
/**
* Adds a new header. Used only by the caching to simulate
* If-Modified-Since.
*
* @param key the key of the new header
* @param value the value for the new header
*/
public void setHeader(String key, String value)
{
int tail;
if (_headerSize > 0) {
tail = (_headerValues[_headerSize - 1].getOffset() +
_headerValues[_headerSize - 1].getLength());
}
else
tail = 0;
char []headerBuffer = _headerBuffer;
for (int i = key.length() - 1; i >= 0; i--)
headerBuffer[tail + i] = key.charAt(i);
_headerKeys[_headerSize].init(headerBuffer, tail, key.length());
tail += key.length();
for (int i = value.length() - 1; i >= 0; i--)
headerBuffer[tail + i] = value.charAt(i);
_headerValues[_headerSize].init(headerBuffer, tail, value.length());
_headerSize++;
}
/**
* Returns the number of headers.
*/
@Override
public int getHeaderSize()
{
return _headerSize;
}
/**
* Returns the header key
*/
@Override
public CharSegment getHeaderKey(int index)
{
return _headerKeys[index];
}
/**
* Returns the header value
*/
@Override
public CharSegment getHeaderValue(int index)
{
return _headerValues[index];
}
/**
* Returns the header.
*/
public String getHeader(String key)
{
CharSegment buf = getHeaderBuffer(key);
if (buf != null)
return buf.toString();
else
return null;
}
/**
* Returns the matching header.
*
* @param testBuf header key
* @param length length of the key.
*/
public CharSegment getHeaderBuffer(char []testBuf, int length)
{
char []keyBuf = _headerBuffer;
CharSegment []headerKeys = _headerKeys;
for (int i = _headerSize - 1; i >= 0; i--) {
CharSegment key = headerKeys[i];
if (key.length() != length)
continue;
int offset = key.getOffset();
int j;
for (j = length - 1; j >= 0; j--) {
char a = testBuf[j];
char b = keyBuf[offset + j];
if (a == b)
continue;
if (a >= 'A' && a <= 'Z')
a += 'a' - 'A';
if (b >= 'A' && b <= 'Z')
b += 'a' - 'A';
if (a != b)
break;
}
if (j < 0)
return _headerValues[i];
}
return null;
}
/**
* Returns the header value for the key, returned as a CharSegment.
*/
public CharSegment getHeaderBuffer(String key)
{
int i = matchNextHeader(0, key);
if (i >= 0)
return _headerValues[i];
else
return null;
}
/**
* Fills an ArrayList with the header values matching the key.
*
* @param values ArrayList which will contain the maching values.
* @param key the header key to select.
*/
public void getHeaderBuffers(String key, ArrayList<CharSegment> values)
{
int i = -1;
while ((i = matchNextHeader(i + 1, key)) >= 0)
values.add(_headerValues[i]);
}
/**
* Return an enumeration of headers matching a key.
*
* @param key the header key to match.
* @return the enumeration of the headers.
*/
public Enumeration getHeaders(String key)
{
ArrayList<String> values = new ArrayList<String>();
int i = -1;
while ((i = matchNextHeader(i + 1, key)) >= 0)
values.add(_headerValues[i].toString());
return Collections.enumeration(values);
}
/**
* Returns the index of the next header matching the key.
*
* @param i header index to start search
* @param key header key to match
*
* @return the index of the next header matching, or -1.
*/
private int matchNextHeader(int i, String key)
{
int size = _headerSize;
int length = key.length();
char []keyBuf = _headerBuffer;
for (; i < size; i++) {
CharSegment header = _headerKeys[i];
if (header.length() != length)
continue;
int offset = header.getOffset();
int j;
for (j = 0; j < length; j++) {
char a = key.charAt(j);
char b = keyBuf[offset + j];
if (a == b)
continue;
if (a >= 'A' && a <= 'Z')
a += 'a' - 'A';
if (b >= 'A' && b <= 'Z')
b += 'a' - 'A';
if (a != b)
break;
}
if (j == length)
return i;
}
return -1;
}
/**
* Returns an enumeration of all the header keys.
*/
public Enumeration getHeaderNames()
{
ArrayList<String> names = new ArrayList<String>();
for (int i = 0; i < _headerSize; i++) {
CharSegment name = _headerKeys[i];
int j;
for (j = 0; j < names.size(); j++) {
String oldName = names.get(j);
if (name.matches(oldName))
break;
}
if (j == names.size())
names.add(j, name.toString());
}
return Collections.enumeration(names);
}
/**
* Returns a stream for reading POST data.
*/
public boolean initStream(ReadStream readStream, ReadStream rawRead)
throws IOException
{
readStream.init(_filter, null);
return true;
}
/**
* Handles a comet-style resume.
*
* @return true if the connection should stay open (keepalive)
*/
/*
@Override
public boolean handleResume()
throws IOException
{
try {
startInvocation();
if (! isComet())
return false;
String url = _tcpConn.getCometPath();
// servlet 3.0 spec defaults to suspend
_tcpConn.suspend();
if (url != null) {
WebApp webApp = getWebApp();
RequestDispatcherImpl disp
= (RequestDispatcherImpl) webApp.getRequestDispatcher(url);
if (disp != null) {
disp.forwardResume(_requestFacade, _responseFacade);
return isSuspend();
}
}
_invocation.doResume(_requestFacade, _responseFacade);
} catch (ClientDisconnectException e) {
_response.killCache();
throw e;
} catch (Throwable e) {
log.log(Level.FINE, e.toString(), e);
// isResume = false;
_response.killCache();
killKeepalive();
return false;
} finally {
finishInvocation();
if (! isSuspend())
finishRequest();
}
if (log.isLoggable(Level.FINE)) {
log.fine(dbgId() +
(isKeepalive() ? "keepalive" : "no-keepalive"));
}
return isSuspend();
}
*/
/**
* Returns true for the top-level request, but false for any include()
* or forward()
*/
public boolean isTop()
{
return true;
}
protected boolean checkLogin()
{
return true;
}
/**
* Clear the request variables in preparation for a new request.
*
* @param s the read stream for the request
*/
@Override
protected void startRequest(HttpBufferStore httpBuffer)
throws IOException
{
super.startRequest(httpBuffer);
_method.clear();
_methodString = null;
_protocol.clear();
_uriLength = 0;
_uri = httpBuffer.getUriBuffer();
_uriHost.clear();
_host = null;
_headerSize = 0;
_headerOffset = 0;
_headerBuffer = httpBuffer.getHeaderBuffer();
_headerKeys = httpBuffer.getHeaderKeys();
_headerValues = httpBuffer.getHeaderValues();
_initAttributes = false;
}
/**
* Returns true for a secure connection.
*/
public boolean isSecure()
{
return _isSecure;
}
/**
* Read the first line of a request:
*
* GET [http://www.caucho.com[:80]]/path [HTTP/1.x]
*
* @return true if the request is valid
*/
private boolean readPacket(ReadStream is)
throws IOException
{
int version = is.read();
int code = is.read();
int id = (is.read() << 8) + is.read();
int len = (is.read() << 8) + is.read();
int pad = is.read();
int reserved = is.read();
if (reserved < 0) {
// end of file
return false;
}
if (version != FCGI_VERSION_1) {
log.warning(this + " unexpected fastcgi version '" + version + "'");
return false;
}
switch (code) {
case FCGI_BEGIN_REQUEST:
return readBeginRequest(is, id);
case FCGI_PARAMS:
return readParams(is, id, len, pad);
case FCGI_STDIN:
return readStdin(is, id, len, pad);
default:
log.warning(this + " unexpected fastcgi code '" + code + "'");
return false;
}
/*
int i = 0;
byte []readBuffer = s.getBuffer();
int readOffset = s.getOffset();
int readLength = s.getLength();
int ch;
if (readOffset >= readLength) {
try {
if ((readLength = s.fillBuffer()) < 0)
return false;
} catch (InterruptedIOException e) {
log.fine(dbgId() + "keepalive timeout");
return false;
}
readOffset = 0;
}
ch = readBuffer[readOffset++];
// conn.setAccessTime(getDate());
// skip leading whitespace
while (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n') {
if (readOffset >= readLength) {
if ((readLength = s.fillBuffer()) < 0)
return false;
readOffset = 0;
}
ch = readBuffer[readOffset++];
}
char []buffer = _method.getBuffer();
int length = buffer.length;
int offset = 0;
// scan method
while (true) {
if (length <= offset) {
}
else if (ch >= 'a' && ch <= 'z')
buffer[offset++] = ((char) (ch + 'A' - 'a'));
else if (ch > ' ')
buffer[offset++] = (char) ch;
else
break;
if (readLength <= readOffset) {
if ((readLength = s.fillBuffer()) < 0)
return false;
readOffset = 0;
}
ch = readBuffer[readOffset++];
}
_method.setLength(offset);
// skip whitespace
while (ch == ' ' || ch == '\t') {
if (readOffset >= readLength) {
if ((readLength = s.fillBuffer()) < 0)
return false;
readOffset = 0;
}
ch = readBuffer[readOffset++];
}
byte []uriBuffer = _uri;
int uriLength = 0;
// skip 'http:'
if (ch != '/') {
while (ch > ' ' && ch != '/') {
if (readOffset >= readLength) {
if ((readLength = s.fillBuffer()) < 0)
return false;
readOffset = 0;
}
ch = readBuffer[readOffset++];
}
if (readOffset >= readLength) {
if ((readLength = s.fillBuffer()) < 0) {
if (ch == '/') {
uriBuffer[uriLength++] = (byte) ch;
_uriLength = uriLength;
}
return true;
}
readOffset = 0;
}
int ch1 = readBuffer[readOffset++];
if (ch1 != '/') {
uriBuffer[uriLength++] = (byte) ch;
ch = ch1;
}
else {
// read host
host:
while (true) {
if (readOffset >= readLength) {
if ((readLength = s.fillBuffer()) < 0) {
return true;
}
readOffset = 0;
}
ch = readBuffer[readOffset++];
switch (ch) {
case ' ': case '\t': case '\n': case '\r':
break host;
case '?':
break host;
case '/':
break host;
default:
_uriHost.append((char) ch);
break;
}
}
}
}
// read URI
uri:
while (true) {
switch (ch) {
case ' ': case '\t': case '\n': case '\r':
break uri;
default:
// There's no check for overrunning the length because
// allowing resizing would allow a DOS memory attack and
// also lets us save a bit of efficiency.
uriBuffer[uriLength++] = (byte) ch;
break;
}
if (readOffset >= readLength) {
readOffset = 0;
if ((readLength = s.fillBuffer()) < 0) {
_uriLength = uriLength;
return true;
}
}
ch = readBuffer[readOffset++];
}
_uriLength = uriLength;
// skip whitespace
while (ch == ' ' || ch == '\t') {
if (readOffset >= readLength) {
readOffset = 0;
if ((readLength = s.fillBuffer()) < 0)
return true;
}
ch = readBuffer[readOffset++];
}
buffer = _protocol.getBuffer();
length = buffer.length;
offset = 0;
// scan protocol
while (ch != ' ' && ch != '\t' && ch != '\r' && ch != '\n') {
if (offset >= length) {
}
else if (ch >= 'a' && ch <= 'z')
buffer[offset++] = ((char) (ch + 'A' - 'a'));
else
buffer[offset++] = (char) ch;
if (readOffset >= readLength) {
readOffset = 0;
if ((readLength = s.fillBuffer()) < 0) {
_protocol.setLength(offset);
return true;
}
}
ch = readBuffer[readOffset++];
}
_protocol.setLength(offset);
if (offset != 8) {
_protocol.append("HTTP/0.9");
_version = HTTP_0_9;
}
else if (buffer[7] == '1') // && _protocol.equals(_http11Cb))
_version = HTTP_1_1;
else if (buffer[7] == '0') // && _protocol.equals(_http10Cb))
_version = HTTP_1_0;
else
_version = HTTP_0_9;
// skip to end of line
while (ch != '\n') {
if (readOffset >= readLength) {
if ((readLength = s.fillBuffer()) < 0)
return true;
readOffset = 0;
}
ch = readBuffer[readOffset++];
}
s.setOffset(readOffset);
return true;
*/
}
/**
* Read the first line of a request:
*
* GET [http://www.caucho.com[:80]]/path [HTTP/1.x]
*
* @return true if the request is valid
*/
private boolean readBeginRequest(ReadStream is, int id)
throws IOException
{
int role = (is.read() << 8) + is.read();
int flags = is.read();
is.skip(5);
if (role != FCGI_RESPONDER) {
log.warning(this + " does not support role=" + role);
return false;
}
boolean isKeepConn = (flags & FCGI_KEEP_CONN) != 0;
if (! isKeepConn) {
killKeepalive("fcgi request close");
}
return true;
}
/**
* Read the parameters from a request.
*
* @return true if the request is valid
*/
private boolean readParams(ReadStream is, int id, int len, int pad)
throws IOException
{
if (len == 0) {
// end of params
return true;
}
long pos = is.getPosition();
long end = pos + len;
while (is.getPosition() < end) {
int keyLen = is.read();
int valueLen = is.read();
is.readAll(_keyBuffer, 0, keyLen);
readParam(is, _keyBuffer, keyLen, valueLen);
}
is.skip(pad);
return true;
}
/**
* Read the stdin
*
* @return true if the request is valid
*/
private boolean readStdin(ReadStream is, int id, int len, int pad)
throws IOException
{
if (len == 0)
return false;
_filter.setPending(len, pad);
return false;
}
private void readParam(ReadStream is,
byte []key, int keyLength,
int valueLength)
throws IOException
{
int ch = key[0];
switch ((keyLength << 8) | ch) {
case HU_REQUEST_URI:
case HL_REQUEST_URI:
if (isMatch(key, REQUEST_URI, keyLength)) {
is.readAll(_uri, 0, valueLength);
_uriLength = valueLength;
_hasRequest = true;
return;
}
break;
case HU_REQUEST_METHOD:
case HL_REQUEST_METHOD:
if (isMatch(key, REQUEST_METHOD, keyLength)) {
_method.setLength(valueLength);
is.readAll(_method.getBuffer(), 0, valueLength);
return;
}
break;
case HU_SERVER_PROTOCOL:
case HL_SERVER_PROTOCOL:
if (isMatch(key, SERVER_PROTOCOL, keyLength)) {
_protocol.setLength(valueLength);
is.readAll(_protocol.getBuffer(), 0, valueLength);
return;
}
break;
}
CharSegment headerKey = _headerKeys[_headerSize];
CharSegment headerValue = _headerValues[_headerSize];
char []headerBuffer = _headerBuffer;
if (keyLength > 5
&& ch == 'H'
&& key[1] == 'T'
&& key[2] == 'T'
&& key[3] == 'P'
&& key[4] == '_') {
int headerOffset = _headerOffset;
for (int i = 5; i < keyLength; i++) {
ch = (char) (key[i] & 0xff);
if (ch == '_')
ch = '-';
headerBuffer[headerOffset++] = (char) ch;
}
headerKey.init(headerBuffer, _headerOffset, keyLength - 5);
is.readAll(headerBuffer, headerOffset, valueLength);
headerValue.init(headerBuffer, headerOffset, valueLength);
_headerOffset = headerOffset + valueLength;
_headerSize++;
return;
}
is.skip(valueLength);
if (log.isLoggable(Level.FINE))
log.fine(this + " skipping " + new String(key, 0, keyLength));
}
private boolean isMatch(byte []bufferA, byte []bufferB, int length)
{
for (int i = length - 1; i >= 0; i--) {
if (bufferA[i] != bufferB[i])
return false;
}
return true;
}
/**
* Returns the raw input stream.
*/
public ReadStream getRawInput()
{
return getRawRead();
}
/**
* Cleans up at the end of the invocation
*/
/*
@Override
public void finishRequest()
throws IOException
{
super.finishRequest();
skip();
finishResponse();
}
*/
void writeTail()
throws IOException
{
_writeStream.flushBuffer();
int id = 1;
byte []tempBuf = _buffer;
tempBuf[0] = FCGI_VERSION_1;
tempBuf[1] = FCGI_STDOUT;
tempBuf[2] = (byte) (id >> 8);
tempBuf[3] = (byte) (id);
tempBuf[4] = (byte) 0;
tempBuf[5] = (byte) 0;
tempBuf[6] = 0;
tempBuf[7] = 0;
_rawWrite.write(tempBuf, 0, 8);
tempBuf[0] = FCGI_VERSION_1;
tempBuf[1] = FCGI_END_REQUEST;
tempBuf[2] = (byte) (id >> 8);
tempBuf[3] = (byte) (id);
tempBuf[4] = (byte) 0;
tempBuf[5] = (byte) 8;
tempBuf[6] = 0;
tempBuf[7] = 0;
_rawWrite.write(tempBuf, 0, 8);
int status = 0;
tempBuf[0] = (byte) (status >> 24);
tempBuf[1] = (byte) (status >> 16);
tempBuf[2] = (byte) (status >> 8);
tempBuf[3] = (byte) (status);
tempBuf[4] = (byte) FCGI_REQUEST_COMPLETE;
tempBuf[5] = 0;
tempBuf[6] = 0;
tempBuf[7] = 0;
_rawWrite.write(tempBuf, 0, 8);
_rawWrite.flush();
}
protected String dbgId()
{
String serverId = getServer().getServerId();
int connId = getConnectionId();
if ("".equals(serverId))
return "FastCgi[" + connId + "] ";
else
return "FastCgi[" + serverId + ", " + connId + "] ";
}
public String toString()
{
String serverId = getServer().getServerId();
int connId = getConnectionId();
if ("".equals(serverId))
return getClass().getSimpleName() + "[" + connId + "]";
else {
return (getClass().getSimpleName() + "[" + serverId
+ ", " + connId + "]");
}
}
/**
* Implements the protocol for data reads and writes. Data from the
* web server to the JVM must be acked, except for the first data.
* Data back to the web server needs no ack.
*/
static class ServletFilter extends StreamImpl {
private FastCgiRequest _request;
private ReadStream _is;
private WriteStream _os;
private byte []_buffer = new byte[16];
private int _pendingData;
private int _pad;
private boolean _isClosed;
private boolean _isClientClosed;
ServletFilter(FastCgiRequest request)
{
_request = request;
}
void init(ReadStream nextRead, WriteStream nextWrite)
{
_is = nextRead;
_os = nextWrite;
_pendingData = 0;
_isClosed = false;
_isClientClosed = false;
}
void setPending(int pendingData, int pad)
{
_pendingData = pendingData;
_pad = pad;
}
void setClientClosed(boolean isClientClosed)
{
_isClientClosed = isClientClosed;
}
@Override
public boolean canRead()
{
return true;
}
@Override
public int getAvailable()
{
return _pendingData;
}
/**
* Reads available data. If the data needs an ack, then do so.
*/
@Override
public int read(byte []buf, int offset, int length)
throws IOException
{
if (_pendingData <= 0)
return -1;
int sublen = _pendingData;
if (length < sublen)
sublen = length;
ReadStream is = _request.getRawRead();
is.readAll(buf, offset, sublen);
_pendingData -= sublen;
if (_pendingData == 0) {
if (_pad > 0)
is.skip(_pad);
_pad = 0;
int version = is.read();
int code = is.read();
int id = (is.read() << 8) + is.read();
_pendingData = (is.read() << 8) + is.read();
_pad = is.read();
int reserved = is.read();
if (reserved < 0 || code != FCGI_STDIN)
_pendingData = 0;
}
return sublen;
}
@Override
public boolean canWrite()
{
return true;
}
/**
* Send data back to the web server
*/
@Override
public void write(byte []buf, int offset, int length, boolean isEnd)
throws IOException
{
if (log.isLoggable(Level.FINE)) {
log.fine(_request.dbgId() + ":data " + length);
if (log.isLoggable(Level.FINEST))
log.finest(_request.dbgId() + "data <" + new String(buf, offset, length) + ">");
}
byte []tempBuf = _buffer;
while (length > 0) {
int sublen = length;
if (32 * 1024 < sublen)
sublen = 32 * 1024;
int id = 1;
tempBuf[0] = FCGI_VERSION_1;
tempBuf[1] = FCGI_STDOUT;
tempBuf[2] = (byte) (id >> 8);
tempBuf[3] = (byte) (id);
tempBuf[4] = (byte) (sublen >> 8);
tempBuf[5] = (byte) sublen;
tempBuf[6] = 0;
tempBuf[7] = 0;
_os.write(tempBuf, 0, 8);
_os.write(buf, offset, sublen);
length -= sublen;
offset += sublen;
}
}
@Override
public void flush()
throws IOException
{
if (! _request._hasRequest)
return;
if (log.isLoggable(Level.FINE))
log.fine(_request.dbgId() + ":flush");
_os.flush();
}
@Override
public void close()
throws IOException
{
if (_isClosed)
return;
_isClosed = true;
if (_pendingData > 0) {
_is.skip(_pendingData);
_pendingData = 0;
}
boolean keepalive = _request.isKeepalive();
if (! _isClientClosed) {
if (log.isLoggable(Level.FINE)) {
if (keepalive)
log.fine(_request.dbgId() + " quit channel");
else
log.fine(_request.dbgId() + " exit socket");
}
}
if (keepalive)
_os.flush();
else
_os.close();
//nextRead.close();
}
}
}