/*
* Copyright (c) xlightweb.org, 2008 - 2009. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* Please refer to the LGPL license at: http://www.gnu.org/copyleft/lesser.txt
* The latest copy of this software may be found on http://www.xlightweb.org/
*/
package org.xlightweb;
import static org.xlightweb.HttpUtils.AND;
import static org.xlightweb.HttpUtils.COLON;
import static org.xlightweb.HttpUtils.CR;
import static org.xlightweb.HttpUtils.EQUALS;
import static org.xlightweb.HttpUtils.HTAB;
import static org.xlightweb.HttpUtils.LF;
import static org.xlightweb.HttpUtils.MAX_HEADER_SIZE;
import static org.xlightweb.HttpUtils.QUESTION_MARK;
import static org.xlightweb.HttpUtils.SLASH;
import static org.xlightweb.HttpUtils.SPACE;
import java.io.IOException;
import java.nio.ByteBuffer;
import org.xlightweb.AbstractHttpConnection.IMessageHandler;
import org.xlightweb.AbstractHttpConnection.IMessageHeaderHandler;
import org.xsocket.DataConverter;
import org.xsocket.connection.INonBlockingConnection;
/**
* server side http protocol handler
*
* @author grro@xlightweb.org
*/
final class HttpProtocolHandlerServerSide extends AbstractHttpProtocolHandler {
ByteBuffer[] parseHeader(AbstractHttpConnection httpConnection, ByteBuffer[] rawData) throws BadMessageException, IOException {
HttpRequestHeader requestHeader = parse(httpConnection.getUnderlyingTcpConnection(), rawData);
if (requestHeader != null) {
httpConnection.setLastTimeHeaderReceivedMillis(System.currentTimeMillis());
httpConnection.incCountMessageReceived();
IMessageHeaderHandler messageHeaderHandler = httpConnection.getMessageHeaderHandler();
if (messageHeaderHandler == null) {
throw new IOException("no message handler set");
}
HttpRequest request = null;
switch (getBodyType(requestHeader)) {
case BODY_TYPE_EMTPY:
request = new HttpRequest(requestHeader);
IMessageHandler messageHandler = messageHeaderHandler.onMessageHeaderReceived(request);
messageHandler.onMessageReceived();
rawData = HttpUtils.compact(rawData);
reset();
// next request? (-> pipelining)
if (rawData != null) {
return onData(httpConnection, rawData);
}
break;
case FULL_MESSAGE:
AbstractNetworkBodyDataSource dataSource = new FullMessageBodyDataSource(httpConnection, requestHeader.getCharacterEncoding(), requestHeader.getContentLength());
setBodyDataSource(dataSource);
request = new HttpRequest(requestHeader, dataSource);
messageHandler = messageHeaderHandler.onMessageHeaderReceived(request);
setMessageHandler(messageHandler);
setState(RECEIVING_BODY);
request.getNonBlockingBody().setBodyDataReceiveTimeoutMillis(httpConnection.getBodyDataReceiveTimeoutMillis());
return parserBody(httpConnection, rawData);
default: // BODY_TYPE_CHUNKED
dataSource = new FullMessageChunkedBodyDataSource(httpConnection, requestHeader);
setBodyDataSource(dataSource);
request = new HttpRequest(requestHeader, dataSource);
messageHandler = messageHeaderHandler.onMessageHeaderReceived(request);
setMessageHandler(messageHandler);
setState(RECEIVING_BODY);
request.getNonBlockingBody().setBodyDataReceiveTimeoutMillis(httpConnection.getBodyDataReceiveTimeoutMillis());
return parserBody(httpConnection, rawData);
}
}
return rawData;
}
private static int getBodyType(HttpRequestHeader requestHeader) throws BadMessageException {
String method = requestHeader.getMethod();
// GET request?
if (method.equals(IHttpMessage.GET_METHOD)) {
return BODY_TYPE_EMTPY;
}
// POST request?
if (method.equals(IHttpMessage.POST_METHOD)) {
return getBodyTypeByHeader(requestHeader);
}
// other bodyless request?
if (method.equals(IHttpMessage.CONNECT_METHOD) ||
method.equals(IHttpMessage.HEAD_METHOD) ||
method.equals(IHttpMessage.TRACE_METHOD)||
method.equals(IHttpMessage.DELETE_METHOD)||
method.equals(IHttpMessage.OPTIONS_METHOD)) {
return BODY_TYPE_EMTPY;
}
// ... no, determine if body by header
return getBodyTypeByHeader(requestHeader);
}
private static int getBodyTypeByHeader(HttpRequestHeader requestHeader) throws BadMessageException {
// contains a non-zero Content-Length header -> bound body
if ((requestHeader.getContentLength() != -1)) {
if (requestHeader.getContentLength() > 0) {
return FULL_MESSAGE;
}
return BODY_TYPE_EMTPY;
}
// transfer encoding header is set with chunked -> chunked body
String transferEncoding = requestHeader.getTransferEncoding();
if ((transferEncoding != null) && (transferEncoding.equalsIgnoreCase("chunked"))) {
return BODY_TYPE_CHUNKED;
}
throw new BadMessageException(requestHeader.toString());
}
private static HttpRequestHeader parse(INonBlockingConnection connection, ByteBuffer[] rawData) throws BadMessageException, IOException {
// filter CR:
// RFC 2616 (19.3 Tolerant Applications)
// ... The line terminator for message-header fields is the sequence CRLF.
// However, we recommend that applications, when parsing such headers,
// recognize a single LF as a line terminator and ignore the leading CR...
HttpRequestHeader requestHeader = new HttpRequestHeader(connection);
if (rawData == null) {
return null;
}
boolean isMerged = false;
ByteBuffer buffer;
if (rawData.length == 1) {
buffer = rawData[0];
} else {
buffer = HttpUtils.merge(rawData);
isMerged = true;
}
int position = buffer.position();
int limit = buffer.limit();
try {
// looking for first char of method -> P
int posStart;
while (true) {
byte b = buffer.get();
if (b > SPACE) {
posStart = buffer.position();
break;
} else if ((b != CR) && (b != LF)) {
if (HttpUtils.isShowDetailedError()) {
buffer.position(position);
buffer.limit(limit);
throw new BadMessageException("bad request (by parsing method name): " + DataConverter.toString(HttpUtils.copy(buffer)));
} else {
throw new BadMessageException("bad request");
}
}
}
// looking for last char of method -> POST
while (true) {
if (buffer.get() == SPACE) {
int posEnd = buffer.position();
String method = extractString(posStart - 1, posEnd - posStart, buffer);
requestHeader.setMethodSilence(method);
buffer.position(posEnd);
break;
}
}
// looking for first char of path -> POST /
while (true) {
if (buffer.get() > SPACE) {
posStart = buffer.position();
break;
}
}
// looking for last char of path or question mark -> POST /test/resource
while (true) {
byte b = buffer.get();
if (b < 64) { // performance optimization: check only char equals or lower than '?'
int posEnd = buffer.position();
if (b == SPACE) {
String path = extractString(posStart - 1, posEnd - posStart, buffer);
requestHeader.setPathSilence(path);
buffer.position(posEnd);
break;
} else if (b == QUESTION_MARK) {
String path = extractString(posStart - 1, posEnd - posStart, buffer);
requestHeader.setPathSilence(path);
buffer.position(posEnd);
parserQueryParams(buffer, posEnd, requestHeader);
break;
// printable char
} else if (b > SPACE) {
continue; // optimization: printable char: continue
// HTTP 0.9 request handling
} else if (b == LF) {
throw new BadMessageException("simple request messages are not supported");
} else if (b == CR) {
b = buffer.get();
if (b == LF) {
throw new BadMessageException("simple request messages are not supported");
} else {
buffer.position(buffer.position() - 1);
}
}
}
}
// looking for slash -> POST /test/resource HTTP/1.1
while (true) {
byte b = buffer.get();
if (b < 48) { // performance optimization: check only char equals or lower than '/'
if (b == SLASH) {
int posSlash = buffer.position();
String protocolScheme = extractString(posSlash - 5, 4, buffer);
requestHeader.setProtocolSchemeSilence(protocolScheme);
String protocolVersion = extractString(posSlash, 3, buffer);
requestHeader.setProtocolVersionSilence(protocolVersion);
buffer.position(posSlash + 3);
break;
} else if (b >= SPACE) {
continue;
} else if (b != HTAB) {
if (HttpUtils.isShowDetailedError()) {
buffer.position(position);
buffer.limit(limit);
throw new BadMessageException("bad request (by parsing protocol): " + DataConverter.toString(HttpUtils.copy(buffer)));
} else {
throw new BadMessageException("bad message");
}
}
}
}
// looking for LF -> POST /test/resource HTTP/1.1
int posLF = 0;
while (true) {
if (buffer.get() == LF) {
posLF = buffer.position();
break;
}
}
// reading headers
String name = null;
String value = null;
outer : while (true) {
// is first char not printable?
byte firstCharOfLine = buffer.get();
if (firstCharOfLine < 33) {
if (firstCharOfLine == CR) {
byte secondCharOfLine = buffer.get();
if (secondCharOfLine == LF) {
if (name != null) {
requestHeader.addHeader(name, value);
}
if (isMerged) {
updateMergedBuffers(buffer, position, rawData);
}
return requestHeader;
}
} else if (firstCharOfLine == LF) {
if (name != null) {
requestHeader.addHeader(name, value);
}
if (isMerged) {
updateMergedBuffers(buffer, position, rawData);
}
return requestHeader;
// Folding
} else if ((firstCharOfLine == SPACE) || (firstCharOfLine == HTAB)) {
// read first non WSP char
int pos = 0;
while (true) {
byte c = buffer.get();
if ((c != SPACE) && (c != HTAB)) {
pos = buffer.position();
break;
}
}
// looking for LF
while (true) {
if (buffer.get() == LF) {
posLF = buffer.position();
String v2 = extractStringWithoutTailingCR(pos - 1, posLF - pos, buffer);
if (value == null) {
value = v2;
} else {
value = value + " " + v2;
}
value = value.trim();
buffer.position(posLF);
continue outer;
}
}
}
} else {
if (name != null) {
requestHeader.addHeader(name, value);
}
}
// looking for colon -> Content-Length:
int posColon;
while (true) {
if (buffer.get() == COLON) {
posColon = buffer.position();
name = extractString(posLF, posColon - (posLF + 1), buffer);
buffer.position(posColon + 1);
break;
}
}
// looking for LF -> Content-Length: 143
while (true) {
if (buffer.get() == LF) {
posLF = buffer.position();
value = extractStringWithoutTailingCR(posColon + 1, posLF - (posColon + 2), buffer);
value = value.trim();
buffer.position(posLF);
break;
}
}
}
} catch (BadMessageException bme) {
buffer.position(position);
buffer.limit(limit);
throw bme;
} catch (Exception e) {
buffer.position(position);
buffer.limit(limit);
if (buffer.remaining() > MAX_HEADER_SIZE) {
throw new BadMessageException("max header size reached");
} else {
return null;
}
}
}
private static void parserQueryParams(ByteBuffer buffer, int pos, HttpRequestHeader requestHeader) throws BadMessageException, IOException {
String paramName = null;
String paramValue = "";
boolean isQueryParameterRead = false;
while (true) {
byte b = buffer.get();
if (b < 62) { // performance optimization: check only char equals or lower than '='
// looking for equals -> POST /test/resource?param1=
if (b == EQUALS) {
isQueryParameterRead = true;
paramName = extractString(pos, (buffer.position() - 1) - pos, buffer);
pos = buffer.position() + 1;
// parse value
while (true) {
b = buffer.get();
if (b < 39) { // performance optimization: check only char equals or lower than '&'
// looking for & -> POST /test/resource?param1=value1&
if (b == AND) {
paramValue = extractString(pos, (buffer.position() - 1) - pos, buffer);
pos = buffer.position() + 1;
buffer.position(pos);
requestHeader.addRawQueryParameterSilence(paramName, paramValue);
paramName = null;
paramValue = "";
break;
// or for space -> POST /test/resource?param1=value1
} else if (b == SPACE) {
paramValue = extractString(pos, (buffer.position() - 1) - pos, buffer);
buffer.position(buffer.position() + 1);
requestHeader.addRawQueryParameterSilence(paramName, paramValue);
return;
// printable char
} else if (b > SPACE) {
continue; // optimization: printable char: continue
// HTTP 0.9 request handling
} else if (b == LF) {
throw new BadMessageException("simple request messages are not supported");
} else if (b == CR) {
b = buffer.get();
if (b == LF) {
throw new BadMessageException("simple request messages are not supported");
} else {
buffer.position(buffer.position() - 1);
}
}
}
}
} else if (b == SPACE) {
if (!isQueryParameterRead) {
String queryString = extractString(pos, (buffer.position() - 1) - pos, buffer);
requestHeader.setQueryString(queryString);
}
buffer.position(buffer.position() + 1);
return;
// printable char
} else if (b > SPACE) {
continue;
// HTTP 0.9 request handling
} else if (b == LF) {
throw new BadMessageException("simple request messages are not supported");
} else if (b == CR) {
b = buffer.get();
if (b == LF) {
throw new BadMessageException("simple request messages are not supported");
} else {
buffer.position(buffer.position() - 1);
}
}
}
}
}
@Override
void onDisconnectInHeaderNothingReceived(AbstractHttpConnection httpConnection, ByteBuffer[] rawData) {
// do nothing
}
}