/*
* 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.http;
import java.io.IOException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.caucho.env.meter.ActiveTimeMeter;
import com.caucho.env.meter.AverageMeter;
import com.caucho.env.meter.MeterService;
import com.caucho.network.listen.ProtocolConnection;
import com.caucho.network.listen.SocketLink;
import com.caucho.network.listen.SocketLinkDuplexController;
import com.caucho.network.listen.SocketLinkDuplexListener;
import com.caucho.network.listen.TcpSocketLink;
import com.caucho.server.cluster.Server;
import com.caucho.server.dispatch.BadRequestException;
import com.caucho.server.dispatch.Invocation;
import com.caucho.util.CharBuffer;
import com.caucho.util.CharSegment;
import com.caucho.util.L10N;
import com.caucho.vfs.ClientDisconnectException;
import com.caucho.vfs.QSocket;
import com.caucho.vfs.ReadStream;
import com.caucho.vfs.SocketTimeoutException;
/**
* Handles a new request from an HTTP connection.
*/
public class HttpRequest extends AbstractHttpRequest
implements ProtocolConnection
{
private static final L10N L = new L10N(HttpRequest.class);
private static final Logger log
= Logger.getLogger(HttpRequest.class.getName());
public static final int HTTP_0_9 = 0x0009;
public static final int HTTP_1_0 = 0x0100;
public static final int HTTP_1_1 = 0x0101;
private static final CharBuffer _getCb = new CharBuffer("GET");
private static final CharBuffer _headCb = new CharBuffer("HEAD");
private static final CharBuffer _postCb = new CharBuffer("POST");
private static final String REQUEST_TIME_PROBE
= "Resin|Http|Request";
private static final String REQUEST_READ_BYTES_PROBE
= "Resin|Http|Request Read Bytes";
private static final String REQUEST_WRITE_BYTES_PROBE
= "Resin|Http|Request Write Bytes";
private final CharBuffer _method // "GET"
= new CharBuffer();
private String _methodString;
private final CharBuffer _uriHost // www.caucho.com:8080
= new CharBuffer();
private CharSequence _host;
private byte []_uri; // "/path/test.jsp/Junk?query=7"
private int _uriLength;
private final CharBuffer _protocol // "HTTP/1.0"
= new CharBuffer();
private int _version;
private char []_headerBuffer;
private CharSegment []_headerKeys;
private CharSegment []_headerValues;
private int _headerSize;
private ChunkedInputStream _chunkedInputStream = new ChunkedInputStream();
private ContentLengthStream _contentLengthStream = new ContentLengthStream();
private RawInputStream _rawInputStream = new RawInputStream();
private ActiveTimeMeter _requestTimeProbe;
private AverageMeter _requestReadBytesProbe;
private AverageMeter _requestWriteBytesProbe;
/**
* Creates a new HttpRequest. New connections reuse the request.
*
* @param server the owning server.
*/
public HttpRequest(Server server, SocketLink conn)
{
super(server, conn);
_requestTimeProbe
= MeterService.createActiveTimeMeter(REQUEST_TIME_PROBE);
_requestReadBytesProbe
= MeterService.createAverageMeter(REQUEST_READ_BYTES_PROBE, "");
_requestWriteBytesProbe
= MeterService.createAverageMeter(REQUEST_WRITE_BYTES_PROBE, "");
}
@Override
public HttpResponse createResponse()
{
return new HttpResponse(this, getConnection().getWriteStream());
}
/**
* Return true if the request waits for a read before beginning.
*/
@Override
public final boolean isWaitForRead()
{
return true;
}
/**
* Returns true if the request exists
*/
@Override
public boolean hasRequest()
{
return getRequestFacade() != null;
}
/**
* Returns true for the top-level request, but false for any include()
* or forward()
*/
public boolean isTop()
{
return true;
}
protected boolean checkLogin()
{
return true;
}
//
// HTTP request properties
//
/**
* Returns a buffer containing the request method.
*/
public CharSegment getMethodBuffer()
{
return _method;
}
/**
* Returns the HTTP method (GET, POST, HEAD, etc.)
*/
@Override
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();
}
}
return _methodString;
}
/**
* Returns the virtual host of the request
*/
@Override
protected CharSequence getHost()
{
if (_host != null)
return _host;
String virtualHost = getConnection().getVirtualHost();
if (virtualHost != null)
_host = virtualHost;
else if (_uriHost.length() > 0)
_host = _uriHost;
else
_host = _hostHeader;
return _host;
}
/**
* Returns the virtual host from the invocation
*/
private CharSequence getInvocationHost()
throws IOException
{
if (_host != null)
return _host;
String virtualHost = getConnection().getVirtualHost();
if (virtualHost != null)
return virtualHost;
else if (_host != null) {
}
else if (_uriHost.length() > 0) {
_host = _uriHost;
}
else if (_hostHeader != null) {
_host = _hostHeader;
}
else if (HTTP_1_1 <= getVersion())
throw new BadRequestException("HTTP/1.1 requires a Host header (Remote IP=" + getRemoteHost() + ")");
return _host;
}
/**
* Returns the byte buffer containing the request URI
*/
@Override
public byte []getUriBuffer()
{
return _uri;
}
/**
* Returns the length of the request URI
*/
@Override
public int getUriLength()
{
return _uriLength;
}
/**
* Returns the protocol.
*/
@Override
public String getProtocol()
{
switch (_version) {
case HTTP_1_1:
return "HTTP/1.1";
case HTTP_1_0:
return "HTTP/1.0";
case HTTP_0_9:
default:
return "HTTP/0.9";
}
}
/**
* Returns a char segment containing the protocol.
*/
public CharSegment getProtocolBuffer()
{
return _protocol;
}
/**
* Returns the HTTP version of the request based on getProtocol().
*/
int getVersion()
{
if (_version > 0)
return _version;
CharSegment protocol = getProtocolBuffer();
if (protocol.equals("HTTP/1.1")) {
_version = HTTP_1_1;
return HTTP_1_1;
}
else if (protocol.equals("HTTP/1.0")) {
_version = HTTP_1_0;
return _version;
}
else if (protocol.equals("HTTP/0.9")) {
_version = HTTP_0_9;
return HTTP_0_9;
}
else if (protocol.length() < 8) {
_version = HTTP_0_9;
return _version;
}
int i = protocol.indexOf('/');
int len = protocol.length();
int major = 0;
for (i++; i < len; i++) {
char ch = protocol.charAt(i);
if ('0' <= ch && ch <= '9')
major = 10 * major + ch - '0';
else if (ch == '.')
break;
else {
_version = HTTP_1_0;
return _version;
}
}
int minor = 0;
for (i++; i < len; i++) {
char ch = protocol.charAt(i);
if ('0' <= ch && ch <= '9')
minor = 10 * minor + ch - '0';
else
break;
}
_version = 256 * major + minor;
return _version;
}
//
// HTTP request headers
//
/**
* Returns the header.
*/
@Override
public String getHeader(String key)
{
CharSegment buf = getHeaderBuffer(key);
if (buf != null)
return buf.toString();
else
return null;
}
/**
* 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 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.
*/
@Override
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.
*/
@Override
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.
*/
@Override
public Enumeration<String> 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.
*/
@Override
public Enumeration<String> 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);
}
/**
* 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
*/
@Override
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++;
}
//
// attribute management
//
/**
* Initialize any special attributes.
*/
@Override
protected void initAttributes(HttpServletRequestImpl request)
{
SocketLink conn = getConnection();
if (! (conn instanceof TcpSocketLink))
return;
TcpSocketLink tcpConn = (TcpSocketLink) conn;
if (! conn.isSecure())
return;
QSocket socket = tcpConn.getSocket();
String cipherSuite = socket.getCipherSuite();
request.setAttribute("javax.servlet.request.cipher_suite", cipherSuite);
int keySize = socket.getCipherBits();
if (keySize != 0)
request.setAttribute("javax.servlet.request.key_size",
new Integer(keySize));
try {
X509Certificate []certs = socket.getClientCertificates();
if (certs != null && certs.length > 0) {
request.setAttribute("javax.servlet.request.X509Certificate",
certs); //spec mandates array
}
} catch (Exception e) {
log.log(Level.FINER, e.toString(), e);
}
}
//
// session management
//
/**
* For SSL connections, use the SSL identifier.
*/
@Override
public String findSessionIdFromConnection()
{
SocketLink link = getConnection();
TcpSocketLink tcpConn = null;
if (link instanceof TcpSocketLink)
tcpConn = (TcpSocketLink) getConnection();
if (tcpConn == null || ! tcpConn.isSecure())
return null;
/*
QSocket socket = tcpConn.getSocket(); // XXX:
if (! (socket instanceof SSLSocket))
return null;
SSLSession sslSession = ((SSLSocket) socket).getSession();
if (sslSession == null)
return null;
byte []sessionId = sslSession.getId();
if (sessionId == null)
return null;
CharBuffer cb = CharBuffer.allocate();
Base64.encode(cb, sessionId, 0, sessionId.length);
for (int i = cb.length() - 1; i >= 0; i--) {
char ch = cb.charAt(i);
if (ch == '/')
cb.setCharAt(i, '-');
}
return cb.close();
*/
return null;
}
//
// stream management
//
/**
* Returns a stream for reading POST data.
*/
@Override
public boolean initStream(ReadStream readStream, ReadStream rawRead)
throws IOException
{
// ReadStream readStream = getReadStream();
// needed to avoid auto-flush on read conflicting with partially
// generated response
rawRead.setSibling(null);
if (getConnection().isDuplex()) {
_rawInputStream.init(rawRead);
readStream.init(_rawInputStream, null);
return true;
}
long contentLength = getLongContentLength();
if (contentLength < 0 && HTTP_1_1 <= getVersion()
&& getHeader("Transfer-Encoding") != null) {
_chunkedInputStream.init(rawRead);
readStream.init(_chunkedInputStream, null);
return true;
}
// Otherwise use content-length
else if (contentLength >= 0) {
_contentLengthStream.init(rawRead, contentLength);
readStream.init(_contentLengthStream, null);
return true;
}
else if (getMethod().equals("POST")) {
_contentLengthStream.init(rawRead, 0);
readStream.init(_contentLengthStream, null);
throw new com.caucho.server.dispatch.BadRequestException("POST requires content-length");
}
else {
_contentLengthStream.init(rawRead, 0);
readStream.init(_contentLengthStream, null);
return false;
}
}
@Override
protected void skip()
throws IOException
{
if (getMethod() == "GET")
return;
super.skip();
}
/**
* Returns the raw input stream.
*/
@Override
public ReadStream getRawInput()
{
return getRawRead();
}
/**
* Handles a new HTTP request.
*
* <p>Note: ClientDisconnectException must be rethrown to
* the caller.
*
* @return true if the connection should stay open (keepalive)
*/
@Override
public boolean handleRequest()
throws IOException
{
boolean isInvocation = false;
Server server = getServer();
Thread thread = Thread.currentThread();
ClassLoader oldLoader = thread.getContextClassLoader();
long startTime = 0;
long startReadBytes = getRawRead().getPosition();
long startWriteBytes = getRawWrite().getPosition();
try {
thread.setContextClassLoader(server.getClassLoader());
startRequest(server.allocateHttpBuffer());
if (! parseRequest()) {
if (log.isLoggable(Level.FINER)) {
log.finer(dbgId() + " empty request");
}
return false;
}
CharSequence host = getInvocationHost();
Invocation invocation = getInvocation(host, _uri, _uriLength);
if (invocation == null) {
if (log.isLoggable(Level.FINER)) {
log.finer(dbgId() + " empty invocation");
}
return false;
}
HttpServletRequestImpl requestFacade = getRequestFacade();
requestFacade.setInvocation(invocation);
isInvocation = true;
startTime = _requestTimeProbe.start();
startInvocation();
invocation.service(requestFacade, getResponseFacade());
} catch (ClientDisconnectException e) {
clientDisconnect();
/*
CauchoResponse response = getResponseFacade();
if (response != null)
response.killCache();
killKeepalive();
*/
throw e;
} catch (Throwable e) {
log.log(Level.FINE, e.toString(), e);
CauchoResponse response = getResponseFacade();
if (response != null)
response.killCache();
killKeepalive("http handleRequest exception: " + e);
sendRequestError(e);
return false;
} finally {
if (isInvocation) {
finishInvocation();
}
if (! isSuspend()) {
finishRequest();
}
if (startTime > 0) {
_requestTimeProbe.end(startTime);
long endReadBytes = getRawRead().getPosition();
long endWriteBytes = getRawWrite().getPosition();
_requestReadBytesProbe.add(endReadBytes - startReadBytes);
_requestWriteBytesProbe.add(endWriteBytes - startWriteBytes);
}
thread.setContextClassLoader(oldLoader);
}
return true;
}
private boolean parseRequest()
throws IOException
{
try {
ReadStream is = getRawRead();
if (! readRequest(is)) {
clearRequest();
return false;
}
if (log.isLoggable(Level.FINE)) {
log.fine(dbgId() + _method + " "
+ new String(_uri, 0, _uriLength) + " " + _protocol);
log.fine(dbgId() + "Remote-IP: " + getRemoteHost()
+ ":" + getRemotePort());
}
parseHeaders(is);
return true;
} catch (ClientDisconnectException e) {
throw e;
} catch (SocketTimeoutException e) {
log.log(Level.FINER, e.toString(), e);
return false;
} catch (ArrayIndexOutOfBoundsException e) {
log.log(Level.FINER, e.toString(), e);
throw new BadRequestException(L.l("Invalid request: URL or headers are too long"), e);
} catch (Throwable e) {
log.log(Level.FINER, e.toString(), e);
throw new BadRequestException(String.valueOf(e), e);
}
}
/**
* 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;
_headerBuffer = httpBuffer.getHeaderBuffer();
_headerKeys = httpBuffer.getHeaderKeys();
_headerValues = httpBuffer.getHeaderValues();
}
/**
* 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 readRequest(ReadStream s)
throws IOException
{
// server/12o3 - default to 1.0 for error messages in request
_version = HTTP_1_0;
byte []readBuffer = s.getBuffer();
int readOffset = s.getOffset();
int readLength = s.getLength();
int ch;
// skip leading whitespace
do {
if (readLength <= readOffset) {
if ((readLength = s.fillBuffer()) < 0)
return false;
readOffset = 0;
}
ch = readBuffer[readOffset++];
} while (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n');
char []buffer = _method.getBuffer();
int length = buffer.length;
int offset = 0;
// scan method
while (true) {
if (length <= offset) {
}
else if ('a' <= ch && 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 (readLength <= readOffset) {
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 (ch != '/') {
log.warning("Invalid Request (method=" + _method + " url ch=0x" + Integer.toHexString(ch) + ") (IP=" + getRemoteHost() + ")");
throw new BadRequestException("Invalid Request(Remote IP=" + getRemoteHost() + ")");
}
if (readLength <= readOffset) {
if ((readLength = s.fillBuffer()) < 0) {
if (ch == '/') {
uriBuffer[uriLength++] = (byte) ch;
_uriLength = uriLength;
}
_version = 0;
return true;
}
readOffset = 0;
}
int ch1 = readBuffer[readOffset++];
if (ch1 != '/') {
uriBuffer[uriLength++] = (byte) ch;
ch = ch1;
}
else {
// read host
host:
while (true) {
if (readLength <= readOffset) {
if ((readLength = s.fillBuffer()) < 0) {
_version = 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 over-running 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 (readLength <= readOffset) {
readOffset = 0;
if ((readLength = s.fillBuffer()) < 0) {
_uriLength = uriLength;
_version = 0;
return true;
}
}
ch = readBuffer[readOffset++];
}
_uriLength = uriLength;
_version = 0;
// skip whitespace
while (ch == ' ' || ch == '\t') {
if (readLength <= readOffset) {
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 (length <= offset) {
}
else if ('a' <= ch && ch <= 'z')
buffer[offset++] = ((char) (ch + 'A' - 'a'));
else
buffer[offset++] = (char) ch;
if (readLength <= readOffset) {
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 (readLength <= readOffset) {
if ((readLength = s.fillBuffer()) < 0)
return true;
readOffset = 0;
}
ch = readBuffer[readOffset++];
}
s.setOffset(readOffset);
return true;
}
/**
* Parses headers from the read stream.
*
* @param s the input read stream
*/
private void parseHeaders(ReadStream s) throws IOException
{
// This is still slowest part of the web server. I don't see how
// to improve it much more, but there must be a way.
int version = getVersion();
if (version < HTTP_1_0) {
return;
}
if (version < HTTP_1_1)
killKeepalive("http client version less than 1.1: " + version);
byte []readBuffer = s.getBuffer();
int readOffset = s.getOffset();
int readLength = s.getLength();
char []headerBuffer = _headerBuffer;
int headerOffset = 1;
headerBuffer[0] = 'z';
int headerSize = 0;
_headerSize = 0;
CharSegment []headerKeys = _headerKeys;
CharSegment []headerValues = _headerValues;
boolean isLogFine = log.isLoggable(Level.FINE);
while (true) {
int ch;
int keyOffset = headerOffset;
// scan the key
while (true) {
if (readLength <= readOffset) {
readOffset = 0;
if ((readLength = s.fillBuffer()) <= 0)
return;
}
ch = readBuffer[readOffset++];
if (ch == '\n') {
s.setOffset(readOffset);
return;
}
else if (ch == ':')
break;
headerBuffer[headerOffset++] = (char) ch;
}
while (headerBuffer[headerOffset - 1] == ' ')
headerOffset--;
int keyLength = headerOffset - keyOffset;
headerKeys[headerSize].init(headerBuffer, keyOffset, keyLength);
do {
if (readLength <= readOffset) {
readOffset = 0;
if ((readLength = s.fillBuffer()) <= 0)
return;
}
ch = readBuffer[readOffset++];
} while (ch == ' ' || ch == '\t');
int valueOffset = headerOffset;
// scan the value
while (true) {
if (readLength <= readOffset) {
readOffset = 0;
if ((readLength = s.fillBuffer()) <= 0)
break;
}
if (ch == '\n') {
int ch1 = readBuffer[readOffset];
if (ch1 == ' ' || ch1 == '\t') {
ch = ' ';
readOffset++;
if (headerBuffer[headerOffset - 1] == '\r')
headerOffset--;
}
else
break;
}
headerBuffer[headerOffset++] = (char) ch;
ch = readBuffer[readOffset++];
}
while (headerBuffer[headerOffset - 1] <= ' ')
headerOffset--;
int valueLength = headerOffset - valueOffset;
headerValues[headerSize].init(headerBuffer, valueOffset, valueLength);
if (isLogFine) {
log.fine(dbgId() +
headerKeys[headerSize] + ": " + headerValues[headerSize]);
}
if (addHeaderInt(headerBuffer, keyOffset, keyLength,
headerValues[headerSize])) {
headerSize++;
}
_headerSize = headerSize;
}
}
//
// upgrade to duplex
//
/**
* Upgrade to duplex
*/
@Override
public SocketLinkDuplexController startDuplex(SocketLinkDuplexListener handler)
{
TcpSocketLink conn = (TcpSocketLink) getConnection();
SocketLinkDuplexController context = conn.startDuplex(handler);
_rawInputStream.init(conn.getReadStream());
getReadStream().setSource(_rawInputStream);
return context;
}
/**
* Cleans up at the end of the invocation
*/
@Override
public void finishRequest()
throws IOException
{
super.finishRequest();
skip();
}
@Override
protected String dbgId()
{
String serverId = getServer().getServerId();
int connId = getConnectionId();
if ("".equals(serverId))
return "Http[" + connId + "] ";
else
return "Http[" + serverId + ", " + connId + "] ";
}
@Override
public String toString()
{
String serverId = getServer().getServerId();
int connId = getConnectionId();
if ("".equals(serverId))
return "HttpRequest[" + connId + "]";
else {
return ("HttpRequest[" + serverId + ", " + connId + "]");
}
}
}