All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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