
org.xlightweb.HttpProtocolHandlerClientSide Maven / Gradle / Ivy
/*
* Copyright (c) xlightweb.org, 2008 - 2010. 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.LF;
import static org.xlightweb.HttpUtils.MAX_HEADER_SIZE;
import static org.xlightweb.HttpUtils.SLASH;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.xlightweb.AbstractHttpConnection.IMessageHandler;
import org.xlightweb.AbstractHttpConnection.IMessageHeaderHandler;
import org.xsocket.DataConverter;
import org.xsocket.connection.INonBlockingConnection;
/**
* client side http protocol handler
*
* @author [email protected]
*/
final class HttpProtocolHandlerClientSide extends AbstractHttpProtocolHandler {
private static final Logger LOG = Logger.getLogger(HttpProtocolHandlerClientSide.class.getName());
ByteBuffer[] parseHeader(AbstractHttpConnection httpConnection, ByteBuffer[] rawData) throws BadMessageException, IOException {
HttpResponseHeader responseHeader = null;
responseHeader = parse(httpConnection.getUnderlyingTcpConnection(), rawData);
if (responseHeader != null) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("[" + httpConnection.getId() + "] request header parsed (rawData: " + HttpUtils.computeRemaining(rawData) + ")");
}
responseHeader.setBodyDefaultEncoding(httpConnection.getBodyDefaultEncoding());
setState(RECEIVING_BODY);
httpConnection.setLastTimeHeaderReceivedMillis(System.currentTimeMillis());
httpConnection.incCountMessageReceived();
IMessageHeaderHandler messageHeaderHandler = httpConnection.getMessageHeaderHandler();
if (messageHeaderHandler == null) {
throw new IOException("no message handler set");
}
try {
int bodyType = getBodyType(httpConnection, responseHeader, (IHttpRequestHeader) messageHeaderHandler.getAssociatedHeader());
switch (bodyType) {
case BODY_TYPE_EMTPY:
HttpResponse response = new HttpResponse(responseHeader);
IMessageHandler messageHandler = messageHeaderHandler.onMessageHeaderReceived(response);
messageHandler.onHeaderProcessed();
httpConnection.onMessageCompleteReceived(responseHeader);
setMessageHandler(messageHandler);
reset();
// next response? (-> pipelining)
if (rawData != null) {
return onData(httpConnection, rawData);
}
break;
case FULL_MESSAGE:
AbstractNetworkBodyDataSource dataSource = new FullMessageBodyDataSource(responseHeader, httpConnection);
setBodyDataSource(dataSource);
response = new HttpResponse(responseHeader, dataSource);
messageHandler = messageHeaderHandler.onMessageHeaderReceived(response);
try {
setMessageHandler(messageHandler);
response.getNonBlockingBody().setBodyDataReceiveTimeoutMillis(httpConnection.getBodyDataReceiveTimeoutMillis());
rawData = parserBody(httpConnection, rawData);
} finally {
messageHandler.onHeaderProcessed();
}
break;
case BODY_TYPE_SIMPLE:
dataSource = new SimpleMessageBodyDataSource(responseHeader, httpConnection);
setBodyDataSource(dataSource);
response = new HttpResponse(responseHeader, dataSource);
messageHandler = messageHeaderHandler.onMessageHeaderReceived(response);
try {
setMessageHandler(messageHandler);
response.getNonBlockingBody().setBodyDataReceiveTimeoutMillis(httpConnection.getBodyDataReceiveTimeoutMillis());
rawData = parserBody(httpConnection, rawData);
} finally {
messageHandler.onHeaderProcessed();
}
break;
case BODY_TYPE_MULTIPART_BYTERANGE:
dataSource = new MultipartByteRangeMessageBodyDataSource(httpConnection, responseHeader);
setBodyDataSource(dataSource);
response = new HttpResponse(responseHeader, dataSource);
messageHandler = messageHeaderHandler.onMessageHeaderReceived(response);
try {
setMessageHandler(messageHandler);
response.getNonBlockingBody().setBodyDataReceiveTimeoutMillis(httpConnection.getBodyDataReceiveTimeoutMillis());
rawData = parserBody(httpConnection, rawData);
} finally {
messageHandler.onHeaderProcessed();
}
break;
default: // BODY_TYPE_CHUNKED
dataSource = new FullMessageChunkedBodyDataSource(httpConnection, responseHeader);
setBodyDataSource(dataSource);
response = new HttpResponse(responseHeader, dataSource);
messageHandler = messageHeaderHandler.onMessageHeaderReceived(response);
try {
setMessageHandler(messageHandler);
response.getNonBlockingBody().setBodyDataReceiveTimeoutMillis(httpConnection.getBodyDataReceiveTimeoutMillis());
rawData = parserBody(httpConnection, rawData);
} finally {
messageHandler.onHeaderProcessed();
}
break;
}
} catch (BadMessageException bme) {
throw bme;
}
}
return rawData;
}
/**
* {@inheritDoc}
*/
@Override
void onDisconnectInHeader(AbstractHttpConnection httpConnection, ByteBuffer[] rawData) throws IOException {
ByteBuffer buffer = HttpUtils.duplicateAndMerge(rawData);
int maxReadSize = 8;
if (buffer.remaining() < maxReadSize) {
maxReadSize = buffer.remaining();
}
// looking for slash of protocol -> HTTP/
for (int i = 0; i < maxReadSize; i++) {
if (buffer.get() == SLASH) {
int posSlash = buffer.position();
String protocolScheme = extractString(posSlash - 5, 4, buffer);
if (protocolScheme.equalsIgnoreCase("HTTP")) {
throw new ProtocolException("connection " + httpConnection.getId() + " has been disconnected while receiving header. Already received: " + DataConverter.toString(HttpUtils.copy(rawData), IHttpMessageHeader.DEFAULT_ENCODING), null);
}
}
}
// HTTP 0.9 response
httpConnection.setLastTimeHeaderReceivedMillis(System.currentTimeMillis());
httpConnection.incCountMessageReceived();
HttpResponseHeader responseHeader = new HttpResponseHeader(200);
responseHeader.setProtocolVersionSilence("0.9");
responseHeader.setHeader("Connection", "close");
AbstractNetworkBodyDataSource dataSource = new SimpleMessageBodyDataSource(responseHeader, httpConnection);
setBodyDataSource(dataSource);
setState(RECEIVING_BODY);
HttpResponse response = new HttpResponse(responseHeader, dataSource, true);
IMessageHeaderHandler messageHeaderHandler = httpConnection.getMessageHeaderHandler(); // get it before disconnecting dataSource!
if (messageHeaderHandler == null) {
throw new IOException("no message handler set");
}
dataSource.parse(rawData);
dataSource.onDisconnect();
IMessageHandler messageHandler = messageHeaderHandler.onMessageHeaderReceived(response);
setMessageHandler(messageHandler);
messageHandler.onHeaderProcessed();
}
@Override
void onDisconnectInHeaderNothingReceived(AbstractHttpConnection httpConnection, ByteBuffer[] rawData) {
if (httpConnection.getNumOpenTransactions() == 0) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("no open transactions running. ignore disconnect");
}
// do nothing
} else {
super.onDisconnectInHeaderNothingReceived(httpConnection, rawData);
}
}
private static int getBodyType(AbstractHttpConnection httpConnection, HttpResponseHeader responseHeader, IHttpRequestHeader requestHeader) {
int status = responseHeader.getStatus();
if ((status == 304) || // not modified
(status == 204) || // no content
(status == 205) || // reset content
(status == 100) || // continue
(status == 101) || // Switching Protocols
(requestHeader.getMethod().equals(IHttpMessage.HEAD_METHOD))) {
return BODY_TYPE_EMTPY;
}
if (((status / 100) == 2) && requestHeader.getMethod().equals(IHttpMessage.CONNECT_METHOD)) {
return BODY_TYPE_EMTPY;
}
// contains a non-zero Content-Length header -> bound body
if ((responseHeader.getContentLength() != -1)) {
if (responseHeader.getContentLength() > 0) {
return FULL_MESSAGE;
}
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;
}
// is connection header set with close?
if ((responseHeader.getHeader("Connection") != null) && (responseHeader.getHeader("Connection").equalsIgnoreCase("close"))) {
httpConnection.setPersistent(false);
return BODY_TYPE_SIMPLE;
}
// multipart/byteranges response?
if ((responseHeader.getStatus() == 206) && (responseHeader.getContentType() != null) && (responseHeader.getContentType().toLowerCase().startsWith("multipart/byteranges"))) {
return BODY_TYPE_MULTIPART_BYTERANGE;
}
// assume 0.9 response
httpConnection.setPersistent(false);
return BODY_TYPE_SIMPLE;
}
private static HttpResponseHeader parse(INonBlockingConnection connection, ByteBuffer[] rawData) throws 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...
HttpResponseHeader responseHeader = new HttpResponseHeader();
if (rawData == null) {
return null;
}
ByteBuffer workBuffer = HttpUtils.duplicateAndMerge(rawData);
int savePosition = workBuffer.position();
try {
// looking for slash of protocol -> HTTP/
while (true) {
if (workBuffer.get() == SLASH) {
int posSlash = workBuffer.position();
if (LOG.isLoggable(Level.FINE)) {
if (posSlash != 5) {
LOG.fine("leading data " + DataConverter.toString(HttpUtils.copy(workBuffer)));
}
}
String protocolScheme = extractString(posSlash - 5, 4, workBuffer);
responseHeader.setProtocolSchemeSilence(protocolScheme);
String protocolVersion = extractString(posSlash, 3, workBuffer);
responseHeader.setProtocolVersionSilence(protocolVersion);
workBuffer.position(posSlash + 4);
break;
}
}
// looking for first char of status -> HTTP/1.1 2
int posStatusCode;
while (true) {
if (workBuffer.get() > 47) {
posStatusCode = workBuffer.position();
int statusCode = extractInt(posStatusCode - 1, 3, workBuffer);
responseHeader.setStatus(statusCode);
workBuffer.position(posStatusCode + 3);
break;
}
}
// looking for LF -> HTTP/1.1 200 OK
int posLF;
while (true) {
if (workBuffer.get() == LF) {
posLF = workBuffer.position();
String reason = extractStringWithoutTailingCR(posStatusCode + 3, posLF - (posStatusCode + 4), workBuffer);
responseHeader.setReason(reason);
workBuffer.position(posLF);
break;
}
}
parseHeaderLines(workBuffer, responseHeader);
skipPositions(workBuffer.position() - savePosition, rawData);
return responseHeader;
} catch (Exception e) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("header parsing exception: " + e.toString());
}
if (workBuffer.position(savePosition).remaining() > MAX_HEADER_SIZE) {
throw new BadMessageException("max header size reached");
} else {
return null;
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy