
org.jwall.web.http.nio.HttpResponseChannel Maven / Gradle / Ivy
/*
* Copyright (C) 2007-2014 Christian Bockermann
*
* This file is part of the web-audit library.
*
* web-audit library 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 3 of the License, or
* (at your option) any later version.
*
* The web-audit 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*
*/
package org.jwall.web.http.nio;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
import java.util.LinkedList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.jwall.web.http.HttpChunk;
import org.jwall.web.http.HttpHeader;
import org.jwall.web.http.HttpResponse;
import org.jwall.web.http.ProtocolException;
/**
*
* This class reads from an incoming stream and buffers it, parsing the
* stream to read the responses.
*
* Optional it can be requested to merge chunked http-data.
* (not implemented yet)
*
* @author [email protected]
*
*/
public class HttpResponseChannel
extends HttpMessageChannel
{
Logger log = LoggerFactory.getLogger("HttpResponseStream.class");
public final static int STATE_READING_CHUNKS = 2;
public final static int STATE_CLOSE_CONNECTION = 3;
int chunkState = 0;
public final static int READING_CHUNK_HEADER = 0;
public final static int READING_CHUNK_BODY = 1;
public final static int READING_CHUNK_TRAILER = 2;
public final static int READING_CRLF = 3;
/* counts the number of errors occured during reading*/
int errors = 0;
int resNum = 0;
int cs = -1;
ByteBuffer chunk = null;
StringBuffer chunkHeader = new StringBuffer();
boolean connectionClose = false;
static int id = 0;
static int myId = 0;
LinkedList chunks = new LinkedList();
/**
* Create a stream of http-response-packets by reading from the
* given input-stream.
*
* @param in
*/
public HttpResponseChannel( ReadableByteChannel in){
super( in );
myId = ++id;
}
public HttpResponse readMessage() throws IOException, TimeOutException, ProtocolException {
if( state == STATE_READING_HEADER ){
header = readHeader();
if( header == null ){
log.debug(this+": unable to read complete header!");
return null;
}
if( header.isChunked() ){
state = STATE_READING_CHUNKS;
chunkState = READING_CHUNK_HEADER;
return new HttpResponse( header, new byte[0] );
}
if( header.isConnectionCloseSet() && header.getHeader( HttpHeader.CONTENT_LENGTH ) == null ){
log.trace( "{} Switching to STATE_CLOSE_CONNECTION", this );
state = STATE_CLOSE_CONNECTION;
}
if( header.getContentLength() == 0 ){
state = STATE_READING_HEADER;
log.debug( "Readirect or the like, with no content, reading header completed!" );
return new HttpResponse( header, new byte[0] );
}
}
if( header == null )
throw new ProtocolException("No header available, though state shows being AFTER reading-header...");
if( header != null && state == STATE_CLOSE_CONNECTION ){
//
// ok, the server closes the connection, thus the body contains all data
// that will be sent until we read 0/EOF bytes...
//
ByteBuffer b = ByteBuffer.allocate( 2 * 1024 ); // we try to read 2k-chunks...
int bytes = in.read( b );
if( bytes > 0 ){
//
// if we read some bytes these will be saved as chunks, as there
// MIGHT some more following...
//
b.flip();
chunks.add( b );
return null;
} else {
//
// as no more bytes can be read, we assume the channel to be empty...
// the only exception from this is: if we did not read ANY bytes until
// now, we assume there will at least be some more coming...
//
if( chunks.isEmpty() )
return new HttpResponse( header, new byte[0] );
int size = 0;
for( ByteBuffer bb : chunks )
size += bb.limit();
// no we merge all the stuff we previously read into the final message body:
//
ByteBuffer body = ByteBuffer.allocate( size );
for( ByteBuffer bb : chunks )
body.put( bb );
log.info(this + " Completed reading body: " + size + " bytes read.");
resNum++;
return new HttpResponse( header, body.array() );
}
}
if( state == STATE_READING_BODY ){
if( header.getContentLength() > 0 ){
ByteBuffer body = readBody( header.getContentLength() );
if( body != null ){
resNum++;
return new HttpResponse( header, body.array() );
}
}
if( header.isChunked() )
state = STATE_READING_CHUNKS;
}
if( state == STATE_READING_CHUNKS ){
HttpChunk ch = readChunk();
if( ch == null )
return null;
resNum++;
return ch;
}
return null;
}
public HttpChunk readChunk()
throws IOException, ProtocolException
{
String line = "";
if( chunkState == READING_CHUNK_HEADER ){
log.debug( this + " Reading chunk header");
//
// TODO: Is this loop really necessary?
//
do {
line = in.readLine();
if( line == null )
return null;
line = line.trim();
if( line.equals("") )
log.debug("Skipping empty line before chunk-header...");
chunkHeader.append( line + HttpHeader.CRLF );
} while( line.equals("") );
//
// now line is our chunk-size-line
//
try {
int colon = line.indexOf(";"); // for lines like a6; test
if(colon > 0)
cs = Integer.parseInt(line.substring(0, colon), 16);
else {
cs = Integer.parseInt(line.trim(), 16);
}
} catch (Exception e) {
System.err.println( this + " Error while parsing chunk-size line: "+line);
e.printStackTrace();
}
log.debug( this + ": Chunk header complete, chunk size is " + cs);
chunk = ByteBuffer.allocate( cs );
chunkState = READING_CHUNK_BODY;
}
//
// BinaryReader should take care to read exactly cs bytes
// even if blocking is necessary
//
if( chunkState == READING_CHUNK_BODY ){
int bytes = 0;
if( chunk.remaining() > 0 ){
//
// if we did not fully read the chunk body, we try
// for more data
//
bytes = in.read( chunk );
log.debug( this + ": Read "+bytes+" bytes, chunk has "+chunk.remaining()+" bytes missing..." );
}
}
if( chunk.remaining() == 0 ) {
//
// ok, we completely read this chunk, now we need to read the trailing CRLF
//
log.debug( this + ": Finished reading chunk, size is " + chunk.limit() );
chunkState = READING_CRLF;
}
//
// this state is only reached if the chunk-data has been completely read
//
if( chunkState == READING_CRLF ){
String l = in.readLine();
if( l != null ){
if( l.trim().equals( "" ) ){
chunkState = READING_CHUNK_HEADER;
log.debug( this + ": Finished reading chunk" );
//
// in case this is the final chunk we need to switch back
// to the non-chunked mode
//
if( chunk.limit() == 0 ){
resNum++;
state = STATE_READING_HEADER;
}
return new HttpChunk( line, chunk );
} else {
throw new ProtocolException( this + ": Error, expected blank-line after chunk, found: " + l );
}
}
}
log.debug( this + ": Reading chunk not completed." );
return null;
}
public String toString(){
return "HttpResponseChannel["+myId+ " response #"+resNum+"] "+status()+" "+chunkState();
}
public String status(){
if( state == STATE_READING_HEADER )
return "_READING_HEADER_";
if( state == STATE_READING_BODY )
return "_READING_BODY_";
if( state == STATE_READING_CHUNKS )
return "_READING_CHUNKS_";
if( state == STATE_CLOSE_CONNECTION )
return "_CLOSING_CONNECTION_";
return "UNKNOWN_STATE";
}
public String chunkState(){
if( chunkState == READING_CHUNK_HEADER )
return "READING_CHUNK_HEADER";
if( chunkState == READING_CHUNK_BODY )
return "READING_CHUNK_BODY";
if( chunkState == READING_CHUNK_TRAILER )
return "READING_CHUNK_TRAILER";
if( chunkState == READING_CRLF )
return "READING_CRLF";
return "UNKNOWN_CHUNK_STATE";
}
/**
* Returns true if the server has send an "Connection: close"
* within the last response.
*
* @return
*/
public boolean isConnectionClosed(){
return connectionClose;
}
public int getNumberOfResponses(){
return resNum;
}
public int getId(){
return myId;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy