/*
* 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 java.io.IOException;
import java.nio.BufferUnderflowException;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.xlightweb.AbstractHttpConnection.IMessageHandler;
import org.xsocket.connection.INonBlockingConnection;
/**
* client side http protocol handler
*
* @author grro@xlightweb.org
*/
final class HttpProtocolHandlerClientSide extends AbstractHttpProtocolHandler {
private static final Logger LOG = Logger.getLogger(HttpProtocolHandlerClientSide.class.getName());
/**
* {@inheritDoc}
*/
protected void onData(AbstractHttpConnection httpConnection, INonBlockingConnection underlyingConnection, ComposedByteBuffer rawData) throws BadMessageException, IOException {
try {
switch (getState()) {
case INIT:
setHeaderParser(HttpResponseHeaderParser.newInstance());
setState(RECEIVING_HEADER);
onData(httpConnection, underlyingConnection, rawData);
return;
case RECEIVING_HEADER:
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("[" + httpConnection.getId() + "] parsing header (available network data " + rawData.available() + ")");
}
parseHeader(httpConnection, underlyingConnection, rawData);
break;
default: // RECEIVING_BODY
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("[" + httpConnection.getId() + "] parsing body (available network data " + rawData.available() + ")");
}
parserBody(rawData);
}
} catch (BufferUnderflowException ignore) { }
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("[" + httpConnection.getId() + "] after parse (available network data " + rawData.available() + ")");
}
}
private void parseHeader(AbstractHttpConnection httpConnection, INonBlockingConnection underlyingConnection, ComposedByteBuffer rawData) throws BadMessageException, IOException {
HttpResponseHeader responseHeader = null;
try {
responseHeader = (HttpResponseHeader) getHeaderParser().parse(underlyingConnection, rawData);
} catch (SimpleResponseMessageException sre) {
httpConnection.setLastTimeHeaderReceivedMillis(System.currentTimeMillis());
httpConnection.incCountMessageReceived();
responseHeader = new HttpResponseHeader(200);
responseHeader.setProtocolVersionSilence("0.9");
responseHeader.setSimpleResponse(true);
setBodyParser(new SimpleMessageBodyParser(httpConnection, responseHeader));
HttpResponse response = new HttpResponse(responseHeader);
response.setBodyDataSourceSilence(getBodyParser().getDataSource());
setState(RECEIVING_BODY);
parserBody(rawData);
IMessageHandler messageHandler = httpConnection.getMessageHandler();
if (messageHandler == null) {
throw new IOException("no message handler set");
}
messageHandler.onMessage(response);
return;
}
if (responseHeader != null) {
httpConnection.setLastTimeHeaderReceivedMillis(System.currentTimeMillis());
httpConnection.incCountMessageReceived();
IMessageHandler messageHandler = httpConnection.getMessageHandler();
if (messageHandler == null) {
throw new IOException("no message handler set");
}
try {
HttpResponse response = null;
switch (getBodyType(httpConnection, responseHeader, messageHandler.isBodylessMessageExpected())) {
case BODY_TYPE_EMTPY:
response = new HttpResponse(responseHeader);
reset();
break;
case BODY_TYPE_BOUND:
setBodyParser(new FullMessageBodyParser(httpConnection, responseHeader));
response = new HttpResponse(responseHeader);
response.setBodyDataSourceSilence(getBodyParser().getDataSource());
setState(RECEIVING_BODY);
response.getNonBlockingBody().setBodyDataReceiveTimeoutMillis(httpConnection.getBodyDataReceiveTimeoutMillis());
parserBody(rawData);
break;
case BODY_TYPE_SIMPLE:
setBodyParser(new SimpleMessageBodyParser(httpConnection, responseHeader));
response = new HttpResponse(responseHeader);
response.setBodyDataSourceSilence(getBodyParser().getDataSource());
setState(RECEIVING_BODY);
response.getNonBlockingBody().setBodyDataReceiveTimeoutMillis(httpConnection.getBodyDataReceiveTimeoutMillis());
parserBody(rawData);
break;
case BODY_TYPE_MULTIPART_BYTERANGE:
setBodyParser(new MultipartByteRangeMessageBodyParser(httpConnection, responseHeader));
response = new HttpResponse(responseHeader);
response.setBodyDataSourceSilence(getBodyParser().getDataSource());
setState(RECEIVING_BODY);
response.getNonBlockingBody().setBodyDataReceiveTimeoutMillis(httpConnection.getBodyDataReceiveTimeoutMillis());
parserBody(rawData);
break;
default: // BODY_TYPE_CHUNKED
setBodyParser(new FullMessageChunkedBodyParser(httpConnection, responseHeader));
response = new HttpResponse(responseHeader);
response.setBodyDataSourceSilence(getBodyParser().getDataSource());
setState(RECEIVING_BODY);
response.getNonBlockingBody().setBodyDataReceiveTimeoutMillis(httpConnection.getBodyDataReceiveTimeoutMillis());
parserBody(rawData);
break;
}
messageHandler.onMessage(response);
return;
} catch (BadMessageException bme) {
throw bme;
} catch (IOException ioe) {
messageHandler.onException(ioe);
throw ioe;
}
}
}
private static int getBodyType(AbstractHttpConnection httpConnection, HttpResponseHeader responseHeader, boolean isBodylessMessageExcpected) throws BadMessageException {
if (isBodylessMessageExcpected) {
return BODY_TYPE_EMTPY;
}
int status = responseHeader.getStatus();
if ((status == 304) || // not modified
(status == 204) || // no content
(status == 100) || // continue
(status == 101)) { // Switching Protocols
return BODY_TYPE_EMTPY;
}
// contains a non-zero Content-Length header -> bound body
if ((responseHeader.getContentLength() != -1)) {
if (responseHeader.getContentLength() > 0) {
return BODY_TYPE_BOUND;
}
return BODY_TYPE_EMTPY;
}
// transfer encoding header is set with chunked -> chunked body
String transferEncoding = responseHeader.getTransferEncoding();
if ((transferEncoding != null) && (transferEncoding.equalsIgnoreCase("chunked"))) {
return BODY_TYPE_CHUNKED;
}
// multipart/byteranges response?
if ((responseHeader.getStatus() == 206) && (responseHeader.getContentType() != null) && (responseHeader.getContentType().toLowerCase().startsWith("multipart/byteranges"))) {
return BODY_TYPE_MULTIPART_BYTERANGE;
}
// is connection header set with close?
if ((responseHeader.getConnection() != null) && (responseHeader.getConnection().equalsIgnoreCase("close"))) {
httpConnection.setPersistent(false);
return BODY_TYPE_SIMPLE;
}
// is content-type set?
if (responseHeader.getContentType() != null) {
httpConnection.setPersistent(false);
return BODY_TYPE_SIMPLE;
}
throw new BadMessageException(responseHeader.toString());
}
}