FileDocCategorySizeDatePackage
Protocol.javaAPI DocJ2ME MIDP 2.074639Thu Nov 07 12:02:22 GMT 2002com.sun.midp.io.j2me.http

Protocol

public class Protocol extends com.sun.midp.io.ConnectionBaseAdapter implements javax.microedition.io.HttpConnection
This class implements the necessary functionality for an HTTP connection.

Fields Summary
protected static final String
HTTP_VERSION
HTTP version string to use with all outgoing HTTP requests.
private static final int
HTTP_OUTPUT_DATA_OFFSET
Where to start the data in the output buffer.
private static final int
HTTP_OUTPUT_EXTRA_ROOM
How must extra room for the chunk terminator.
private static com.sun.midp.security.SecurityToken
classSecurityToken
This class has a different security domain than the MIDlet suite
private static int
inputBufferSize
Default size for input buffer.
private static int
outputBufferSize
Default size for output buffer.
private static int
outputDataSize
How much data can be put in the output buffer.
private static String
http_proxy
The "host:port" value to use for HTTP proxied requests.
private static int
maxNumberOfPersistentConnections
Maximum number of persistent connections.
private static long
connectionLingerTime
Connection linger time in the pool, default 60 seconds.
private static StreamConnectionPool
connectionPool
Persistent connection pool.
private static boolean
nonPersistentFlag
True if com.sun.midp.io.http.force_non_persistent = true.
protected String
saved_url
Saved copy of URL string to be processed.
protected int
default_port
Default port number for this protocol.
protected com.sun.midp.io.HttpUrl
url
Parsed Url.
protected String
hostAndPort
url.host + ":" + url.port.
protected int
responseCode
Numeric code returned from HTTP response header.
protected String
responseMsg
Message string from HTTP response header.
protected com.sun.midp.io.Properties
reqProperties
Collection of request headers as name/value pairs.
protected com.sun.midp.io.Properties
headerFields
Collection of response headers as name/value pairs.
protected String
method
HTTP method type for the current request.
private javax.microedition.io.StreamConnection
streamConnection
Low level socket connection used for the HTTP requests.
private DataOutputStream
streamOutput
Low level socket output stream.
private DataInputStream
streamInput
Low level socket input stream.
private StringBuffer
stringbuffer
A shared temporary header buffer.
private String
httpVer
HTTP version string set with all incoming HTTP responses.
private boolean
ConnectionCloseFlag
Used when appl calls setRequestProperty("Connection", "close").
private int
contentLength
Content-Length from response header, or -1 if missing.
private int
chunksize
Total number of bytes in the current chunk or content-length when data is sent as one big chunk.
private int
totalbytesread
Number of bytes read from the stream for non-chunked data or the bytes read from the current chunk.
private boolean
chunkedIn
True if Transfer-Encoding: chunkedIn.
private boolean
chunkedOut
True if Transfer-Encoding: chunkedOut.
private boolean
firstChunkSent
True after the first chunk has been sent.
private boolean
sendingRequest
True if the request is being sent.
private boolean
requestFinished
True if the entire request has been sent to the server.
private boolean
eof
True if eof seen.
private byte[]
readbuf
Internal stream buffer to minimize the number of TCP socket reads.
private int
bytesleft
Number of bytes left in internal input stream buffer.
private int
bytesread
Number of bytes read from the internal input stream buffer.
private byte[]
writebuf
Buffered data output for content length calculation.
private int
bytesToWrite
Number of bytes of data that need to be written from the buffer.
private com.sun.midp.io.Properties
proxyHeaders
Collection of "Proxy-" headers as name/value pairs.
private byte
handshakeError
Last handshake error.
private boolean
readInProgress
Holds the state the readBytes call. So if close is called in another thread than the read thread the close will be directly on the stream, instead of putting the connection back in the persistent connection pool, forcing an IOException on the read thread.
Constructors Summary
public Protocol()
Create a new instance of this class and intialize variables. Initially an http connection is unconnected to the network.


                            
      
        reqProperties = new Properties();
        headerFields = new Properties();
        stringbuffer = new StringBuffer(32);

        method = GET;
        responseCode = -1;
        protocol = "http";
        default_port = 80;

        if (nonPersistentFlag) {
            ConnectionCloseFlag = true;
        }

        readbuf = new byte[inputBufferSize];

        requiredPermission = Permissions.HTTP;
    
Methods Summary
public intavailable()
Returns the number of bytes that can be read (or skipped over) from this input stream without blocking by the next caller of a method for this input stream. The next caller might be the same thread or another thread.

return
the number of bytes that can be read from this input stream without blocking.
exception
IOException if an I/O error occurs.

        int bytesAvailable;

        /* 
         * Only after all the headers have been processed can
         * an accurate available count be provided.
         */
        if (!requestFinished || eof) {
            return 0;
        }

        /*
         * Regardless of chunked or non-chunked transfers -
         * if data is already buffered return the amount 
         * buffered.
         */
        if (bytesleft > 0) { 
            return bytesleft; 
        }
                
        if (chunkedIn && totalbytesread == chunksize) { 
            /* 
             * Check if a new chunk size header is available.
             */
            return readChunkSizeNonBlocking();
        } 

        /*
         * Otherwise rely on the lower level stream available
         * count for the nonchunked input stream.
         */
        bytesAvailable =  streamInput.available();
        if (chunksize <= bytesAvailable) {
            return chunksize;
        }

        return bytesAvailable;
    
protected voidcloseOutputStream()
Close the OutputStream and transition to connected state.

exception
IOException if the subclass throws one

        try {
            /*
             * Send a request to the web server if there wasn't one
             * sent already
             */
            sendRequest();

            /* Finish the common close processing. */
            super.closeOutputStream();
        } catch (Exception e) {
            /* Finish the common close processing. */
            super.closeOutputStream();
            if (e instanceof IOException) {
                throw (IOException)e;
            }

            throw (RuntimeException)e;
        }
    
protected voidconnect(java.lang.String name, int mode, boolean timeouts)
Provides the connect() method that sets up the connection, but does not actually connect to the server until there's something to do.

Warning: A subclass that implements this method, not call this method and should also implement the disconnect method.

param
name The URL for the connection, without the without the protcol part.
param
mode The access mode, ignored
param
timeouts A flag to indicate that the called wants timeout exceptions, ignored
exception
IllegalArgumentException If a parameter is invalid.
exception
ConnectionNotFoundException If the connection cannot be found.
exception
IOException If some other kind of I/O error occurs.


        this.saved_url = name;

        url = new HttpUrl(protocol, name);

        if (url.port == -1) {
            url.port = default_port;
        }

        if (url.host == null) {
            throw new IllegalArgumentException("missing host in URL");
        }

        hostAndPort = url.host + ":" + url.port;
    
protected javax.microedition.io.StreamConnectionconnect()
Connect to the underlying network TCP transport. If the proxy is configured, connect to it as tunnel first.

Warning: A subclass that implements this method, should not call this method and should implement the disconnect method.

return
network stream connection
exception
IOException is thrown if the connection cannot be opened

        com.sun.midp.io.j2me.socket.Protocol conn;

        verifyPermissionCheck();

        conn = new com.sun.midp.io.j2me.socket.Protocol();

        if (http_proxy == null) {
            conn.openPrim(classSecurityToken, "//" + hostAndPort);

            // Do not delay request since this delays the response.
            conn.setSocketOption(SocketConnection.DELAY, 0);
            return conn;
        }
      
        conn.openPrim(classSecurityToken, "//" + http_proxy);

        // Do not delay request since this delays the response.
        conn.setSocketOption(SocketConnection.DELAY, 0);

        // openData*Stream cannot be call twice, so save them for later
        streamOutput = conn.openDataOutputStream();
        streamInput = conn.openDataInputStream();

        try {
            doTunnelHandshake(streamOutput, streamInput);
        } catch (IOException ioe) {
            String response = ioe.getMessage();

            try {
                disconnect(conn, streamInput, streamOutput);
            } catch (Exception e) {
                // do not over throw the handshake exception
            }

            streamOutput = null;
            streamInput = null;

            if ((response != null) && (response.indexOf(" 500 ") > -1)) {
                throw new ConnectionNotFoundException(response);
            } else {
                throw ioe;
            }
        }

        return conn;
    
protected voiddisconnect()
Disconnect the current low level socket connection. If the connection is an HTTP1.1 connection that connection will be put back in the pool for another session to use to connect.

        if (streamConnection == null) {
            return;
        }

        /*
         * If the response had content length and it was not chunked
         * and the caller did not read more than the content length
         * eof was not set, so do that now.
         */
        if (!eof && !chunkedIn && chunksize >= 0 &&
                totalbytesread == chunksize) {
            eof = true;
        }

        /*
         * reasons for not reusing the connection are:
         *
         * 1. only part of the chucked request body was sent
         * 2. caller left response data in the stream
         * 3. it is a 1.0 connection
         * 4. there was a signal to close the connection
         * 5. reading in progress on this connection in another thread
         */
        synchronized (streamInput) {
            if (readInProgress) {
                // do not save the connection
                ConnectionCloseFlag = true;
            }
        }

        if (!requestFinished || !eof || httpVer.equals("HTTP/1.0") ||
                ConnectionCloseFlag) {
            if (streamConnection instanceof StreamConnectionElement) {
                // we got this connection from the pool
                connectionPool.remove(
                        (StreamConnectionElement)streamConnection);
            } else {
                disconnect(streamConnection, streamInput, streamOutput);
            }

            return;
        }

        if (streamConnection instanceof StreamConnectionElement) {
            // we got this connection from the pool
            connectionPool.returnForReuse(
                   (StreamConnectionElement)streamConnection);
            return;
        }

        // save the connection for reuse
        if (!connectionPool.add(protocol, url.host, url.port,
                 streamConnection, streamOutput, streamInput)) {
            // pool full, disconnect
            disconnect(streamConnection, streamInput, streamOutput);
        }
    
protected voiddisconnect(javax.microedition.io.StreamConnection connection, java.io.InputStream inputStream, java.io.OutputStream outputStream)
Disconnect from the underlying socket transport. Closes the low level socket connection and the input and output streams used by the socket.

Warning: A subclass that implements connect, should also implement this method without calling this method.

param
connection connection return from {@link #connect()}
param
inputStream input stream opened from connection
param
outputStream output stream opened from connection
exception
IOException if an I/O error occurs while the connection is terminated.
exception
IOException is thrown if the connection or associated streams cannot be closed

        try {
            if (connection != null) {
                connection.close();
            }
        } finally {
            try {
                if (outputStream != null) {
                    outputStream.close();
                }
            } finally {
                if (inputStream != null) {
                    inputStream.close();
                }
            }
        }
    
protected voiddoTunnelHandshake(java.io.OutputStream os, java.io.InputStream is)
Connects to the SSL tunnel and completes the intialization of the tunnel (handshake). The handshake based on the Internet-Draft "A. Luotonen, Tunneling TCP based protocols through Web proxy servers, February 1999".

param
os output stream for secure handshake
param
is input stream for secure handshake
exception
IOException is thrown if an error occurs in the SSL handshake

        String required;
        String optional;
        String endOfLine = "\r\n";
        String emptyLine = endOfLine;
        int numberOfKeys;
        StringBuffer temp;
        boolean newline;
        String response;

        /*
         * request = required *optional emptyLine
         * required = "CONNECT" SP HOST ":" PORT SP HTTP_VERSION endOfLine
         * optional = HTTP_HEADER endOfLine ; proxy dependent: most likely
         *                                  ; used for authorization.
         * emptyLine = endOfLine
         * endOfLine = *1CR LF
         *
         * example:
         * CONNECT home.acme.com:443 HTTP/1.0
         * Proxy-Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
         *
         */
        required = "CONNECT " + hostAndPort + " " + HTTP_VERSION + endOfLine;

        os.write(required.getBytes());

        numberOfKeys = proxyHeaders.size();
        for (int i = 0; i < numberOfKeys; i++) {
            optional = proxyHeaders.getKeyAt(i) + ": " +
                       proxyHeaders.getValueAt(i) + endOfLine;
            os.write(optional.getBytes());
        }

        os.write(emptyLine.getBytes());
        os.flush();

        /*
         * response = status *optional emptyLine
         * status = HTTP_VERSION SP STATUSCODE STATUS_MESSAGE *1CR LF
         * optional = HTTP_HEADER *1CR LF
         * emptyLine = *1CR LF
         *
         * example:
         * HTTP/1.0 200 Connection established
         *
         */

        // Read in response until an empty line is found (1*CR LF 1*CR LF)
        temp = new StringBuffer();
        newline = false;
        while (true) {
            int c = is.read();
            if (c == -1) {
                break;
            } else if (c == '\n") {
                if (newline) {
                    break;
                }
                newline = true;
            } else if (c != '\r") {
                newline = false;
            }

            temp.append((char)c);
        }

        if (temp.length() == 0) {
            temp.append("none");
        }

        response = temp.toString();

        if (response.indexOf(" 200 ") == -1) {
            throw new
                IOException("Error initializing HTTP tunnel connection: \n"
                            + response);
        }
    
protected voidfinishRequestGetResponseHeader()
Finish the http request and reads the response headers.

exception
IOException is thrown, if an I/O error occurs for final stream output or on reading the response message line


        // Even if we get an exception this request is finished
        requestFinished = true;

        /*
         * if this is a CHUNKed session write out the last set of CRLF
         */
        if (chunkedOut) {
            /*
             * reference: RFC2616 - section 3 protocol parameters
             * 3.6 transfer coding:
             * 3.6.1 chunked transfer coding:
             * chunk-body = chunk / last chunk / trailer / CRLF
             *         chunk =      chunk-size / chunk-data / CRLF
             *       * last_chunk = "0" / CRLF
             *       * trailer    = " " / CRLF
             * * indicates its done here.
             */
            
            /*
             * write the last chunk (size=0 / CRLF) and the dummy trailer 
             */
            streamOutput.write("0\r\n\r\n".getBytes()); 
        }

        streamOutput.flush();

        readResponseMessage(streamInput);
        readHeaders(streamInput);
        
        /*
         * Ignore a continuation header and read the true headers again.
         * (Bug# 4382226 discovered with Jetty HTTP 1.1 web server.
         */
        if (responseCode == 100) {
            readResponseMessage(streamInput);
            readHeaders(streamInput);
        }
    
public voidflush()
If any output data, turn on chunking send it to the server.

exception
IOException if an I/O error occurs

        if (requestFinished) {
            throw new IllegalStateException(
                "Flush attempted after request finished");
        }

        if (bytesToWrite > 0) {
            sendRequest(true, false);
        } 
    
public longgetDate()
Get the Date header for the current response.

return
timestamp for the data transmission event
exception
IOException is thrown if a network error ocurrs

        return getHeaderFieldDate("date", 0);
    
public java.lang.StringgetEncoding()
Get the Content-Encoding for the current response.

return
encoding type of data to be transmitted after the response headers

        try {
            return getHeaderField("content-encoding");
        } catch (IOException x) {
            return null;
        }

    
public longgetExpiration()
Get the Expires header for the current response.

return
expiration data for the transmitted data
exception
IOException is thrown if a network error ocurrs

        return getHeaderFieldDate("expires", 0);
    
public java.lang.StringgetFile()
Get the file path name parsed from the URL.

return
file path name from the parsed URL

        return url.path;
    
public java.lang.StringgetHeaderField(java.lang.String name)
Get the named header field for the current response.

param
name header field to be examined
return
value of requested header, if found, otherwise null
exception
IOException is thrown if a network error ocurrs

        ensureOpen();

        sendRequest();

        return (headerFields.getProperty(name.toLowerCase()));

    
public java.lang.StringgetHeaderField(int index)
Get the indexed header field for the current response.

param
index header field offset to be examined
return
key name of requested header, if found, otherwise null
exception
IOException is thrown if a network error ocurrs

        ensureOpen();

        sendRequest();
        
        if (index >= headerFields.size()) {
            return null;
        }
        
        return (headerFields.getValueAt(index));
    
public longgetHeaderFieldDate(java.lang.String name, long def)
Get the named header field for the current response and return a date value for the parsed field,with a supplied default value if the field does not exist or can not be parsed cleanly.

param
name of the field to be examined
param
def default value to use, if field is not parsable
return
date value of requested header, if found, otherwise supplied default is returned
exception
IOException is thrown if a network error ocurrs

        ensureOpen();

        sendRequest();

        try {
            return DateParser.parse(getHeaderField(name));
        } catch (NumberFormatException nfe) {
            // fall through
        } catch (IllegalArgumentException iae) {
            // fall through
        } catch (NullPointerException npe) {
            // fall through
        }
        
        return def;
    
public intgetHeaderFieldInt(java.lang.String name, int def)
Get the named header field for the current response and return a numeric value for the parsed field, with a supplied default value if the field does not exist or can not be parsed cleanly.

param
name of the field to be examined
param
def default value to use, if field is not parsable
return
numeric value of requested header, if found, otherwise supplied default is returned
exception
IOException is thrown if a network error ocurrs

        ensureOpen();

        sendRequest();

        try {
            return Integer.parseInt(getHeaderField(name));
        } catch (IllegalArgumentException iae) {
            // fall through
        } catch (NullPointerException npe) {
            // fall through
        }

        return def;
    
public java.lang.StringgetHeaderFieldKey(int index)
Get the indexed header field value for the current response.

param
index header field value offset to be examined
return
value of requested header, if found, otherwise null
exception
IOException is thrown if a network error ocurrs

        ensureOpen();

        sendRequest();

        if (index >= headerFields.size())
            return null;
        
        return ((String)(headerFields.getKeyAt(index)));
    
public java.lang.StringgetHost()
Get the host name parsed from the URL.

return
host name from the parsed URL

        return url.host;
    
public longgetLastModified()
Get the Last-Modified date header for the current response.

return
timestamp for the transmitted data last modification
exception
IOException is thrown if a network error ocurrs

        return getHeaderFieldDate("last-modified", 0);
    
public longgetLength()
Get the Content-Length for the current response.

return
length of data to be transmitted after the response headers

	try {
	    ensureOpen();
	    
	    sendRequest();
	} catch (IOException ioe) {
	    // Fall through to return -1 for length
	}

	return contentLength;

    
public intgetPort()
Get the query string parsed from the URL.

return
query string from the parsed URL

        return url.port;
    
public java.lang.StringgetProtocol()
Get the protocol scheme parsed from the URL.

return
protocol scheme is "http"

        return protocol;
    
public java.lang.StringgetQuery()
Get the query string parsed from the URL.

return
query string from the parsed URL

        return url.query;
    
public java.lang.StringgetRef()
Get the fragment identifier parsed from the URL.

return
reference component from the parsed URL

        return url.fragment;
    
public java.lang.StringgetRequestMethod()
Get the request method of the current connection.

return
request method is GET, HEAD or POST
see
#setRequestMethod

        return method;
    
public java.lang.StringgetRequestProperty(java.lang.String key)
Get the request header value for the named property.

param
key property name of specific HTTP 1.1 header field
return
value of the named property, if found, null otherwise.
see
#setRequestProperty

        
        /* https handles the proxy fields in a different way */
        if (key.startsWith("Proxy-")) {
            return proxyHeaders.getProperty(key);
        }

        return reqProperties.getProperty(key);
    
public intgetResponseCode()
Get the response code of the current request.

return
numeric value of the parsed response code
exception
IOException is thrown if a network error ocurrs

        ensureOpen();

        sendRequest();

        return responseCode;
    
public java.lang.StringgetResponseMessage()
Get the response message of the current request.

return
message associated with the current response header
exception
IOException is thrown if a network error ocurrs

        ensureOpen();

        sendRequest();

        return responseMsg;
    
protected javax.microedition.io.StreamConnectiongetStreamConnection()
Gets the underlying stream connection.

return
underlying stream connection

        return streamConnection;
    
public java.lang.StringgetType()
Get the Content-Type for the current response.

return
MIME type of data to be transmitted after the response header

        try {
            return getHeaderField("content-type");
        } catch (IOException x) {
            return null;
        }

    
public java.lang.StringgetURL()
Get the original URL used to open the HTTP connection.

return
HTTP URL used in the current connection

        /*
         * RFC:  Add back protocol stripped by Content Connection.
         */

        return protocol + ":" + saved_url;
    
public static voidinitSecurityToken(com.sun.midp.security.SecurityToken token)
Initializes the security token for this class, so it can perform actions that a normal MIDlet Suite cannot.

param
token security token for this class.


            
     
        String prop;
        int temp;

        /*
         * Get the proxy here instead of the connector,
         * so when this method subclassed by HTTPS http_proxy will be null
         * and the proxy will not be added into the request.
         */
        http_proxy = Configuration.getProperty("com.sun.midp.io.http.proxy");
        
        /*
         * if first time intialize the connection pool and create
         * a connection (you could create more)
         */
        maxNumberOfPersistentConnections = 1;

        /*
         * bug#4455443 - allows for configuration options to shut off 
         * the persistent connection feature for http 
         */
        String flag = 
            Configuration.getProperty(
                                "com.sun.midp.io.http.force_non_persistent");
        if ((flag != null) && (flag.equals("true"))) {
            nonPersistentFlag = true;
        }

        /*
         * Get the  maximum number of persistent connections
         * from the configuration file.
         */
        prop = Configuration.getProperty(
                            "com.sun.midp.io.http.max_persistent_connections");
        if (prop != null) {
            try {
                temp = Integer.parseInt(prop);
                if (temp <= 0) {
                    maxNumberOfPersistentConnections = temp;
                }
            } catch (NumberFormatException nfe) {
                // keep the default
            }
        }

        // Get how long a not in use connection should stay in the pool.
        prop = Configuration.getProperty(
                   "com.sun.midp.io.http.persistent_connection_linger_time");
        if (prop != null) {
            try {
                temp = Integer.parseInt(prop);
                if (temp >= 0) {
                    connectionLingerTime = temp;
                }
            } catch (NumberFormatException nfe) {
                // keep the default
            }
        }

        connectionPool = new StreamConnectionPool(
                                 maxNumberOfPersistentConnections,
                                 connectionLingerTime);

        /*
         * Get the  maximum number of persistent connections
         * from the configuration file.
         */
        prop = Configuration.getProperty(
                            "com.sun.midp.io.http.inputBufferSize");
        if (prop != null) {
            try {
                temp = Integer.parseInt(prop);
                if (temp <= 0) {
                    inputBufferSize = temp;
                }
            } catch (NumberFormatException nfe) {
                // keep the default
            }
        }

        prop = Configuration.getProperty(
                            "com.sun.midp.io.http.outputBufferSize");
        if (prop != null) {
            try {
                temp = Integer.parseInt(prop);
                if (temp <= 0) {
                    outputBufferSize = temp;
                }
            } catch (NumberFormatException nfe) {
                // keep the default
            }
        }

        outputDataSize = outputBufferSize - HTTP_OUTPUT_DATA_OFFSET -
                         HTTP_OUTPUT_EXTRA_ROOM;
    
        if (classSecurityToken != null) {
            return;
        }
        
        classSecurityToken = token;
    
public java.io.InputStreamopenInputStream()
Open the input stream if it has not already been opened.

exception
IOException is thrown if it has already been opened.
return
input stream for the current connection

        InputStream in;

        /*
         * Call into parent to create input stream passed back to the user
         */
        in = super.openInputStream();
       
        /*
         * Send a request to the web server if there wasn't one
         * sent already
         */
        sendRequest();

        return in;
    
public java.io.OutputStreamopenOutputStream()
Open the output stream if it has not already been opened.

exception
IOException is thrown if it has already been opened.
return
output stream for the current connection

        OutputStream out;

        /*
         * call into parent to create output stream passed back to the user
         */
        out = super.openOutputStream();

        /*
         * Create a byte array output stream for output buffering
         * once the user calls flush() this gets written to stream
         */
        writebuf = new byte[outputBufferSize];

        return out;
    
protected intreadBytes(byte[] b, int off, int len)
Reads up to len bytes of data from the input stream into an array of bytes. This method reads NonChunked http connection input streams. This method can only be called after the InputStream setup is complete.

param
b the buffer into which the data is read.
param
off the start offset in array b at which the data is written.
param
len the maximum number of bytes to read.
return
the total number of bytes read into the buffer, or -1 if there is no more data because the end of the stream has been reached.
exception
IOException if an I/O error occurs.


        int rc;
        
        /*
         * Be consistent about returning EOF once encountered.
         */
        if (eof) {
            return (-1);
        }

        /*
         * The InputStream close behavior will be different if close is called
         * from another thread when reading.
         */
        synchronized (streamInput) {
            readInProgress = true;
        }

        try {
            /*
             * If the http connection is chunked, call the readBytesChunked
             * method
             */
            if (chunkedIn || chunksize > 0) {
                /*
                 * Non-chunked data of known length is treated as one big chunk
                 */
                return readBytesChunked(b, off, len);
            }

            /*
             * Non-chunked unknown length
             */

            if (bytesleft == 0) {
                /*
                 * the internal input stream buffer is empty, read from the
                 * stream
                 */
                if (len >= inputBufferSize) {
                    /*
                     * No need to buffer, if the caller has given a big buffer.
                     */
                    rc = streamInput.read(b, off, len);
                } else {
                    rc = streamInput.read(readbuf, 0, inputBufferSize);
                    bytesleft = rc;
                    bytesread = 0;
                }

                if (rc == -1) {
                    /*
                     * The next call to this method should not read.
                     */
                    eof = true;
                    return -1;
                }

                totalbytesread += rc;

                if (bytesleft == 0) {
                    /*
                     * The data was read directly into the caller's buffer.
                     */
                    return rc;
                }
            }

            rc = readFromBuffer(b, off, len);
            return rc;
        } finally {
            synchronized (streamInput) {
                readInProgress = false;
            }
        }
    
protected intreadBytesChunked(byte[] b, int off, int len)
Reads up to len bytes of data from the input stream into an array of bytes. This method reads Chunked and known length non-chunked http connection input streams. For non-chunked set the field chunkedIn should be false.

param
b the buffer into which the data is read.
param
off the start offset in array b at which the data is written.
param
len the maximum number of bytes to read.
return
the total number of bytes read into the buffer, or -1 if there is no more data because the end of the stream has been reached.
exception
IOException if an I/O error occurs.


        int rc;


        if (bytesleft == 0) {
            /*
             * the internal input stream buffer is empty, read from the stream
             */

            if (totalbytesread == chunksize) {
                /*
                 * read the end of the chunk and get the size of the
                 * the next if there is one
                 */

                if (!chunkedIn) {
                    /*
                     * non-chucked data is treated as one big chunk so there
                     * is no more data so just return as if there are no
                     * more chunks
                     */
                    eof = true;
                    return -1;
                }

                skipEndOfChunkCRLF();

                chunksize = readChunkSize();
                if (chunksize == 0) {
                    eof = true;

                    /*
                     * REFERENCE: HTTP1.1 document 
                     * SECTION: 3.6.1 Chunked Transfer Coding
                     * in some cases there may be an OPTIONAL trailer
                     * containing entity-header fields. since we don't support
                     * the available() method for TCP socket input streams and
                     * for performance and reuse reasons we do not attempt to
                     * clean up the current connections input stream. 
                     * check readResponseMessage() method in this class for
                     * more details
                     */
                    return -1;
                }

                /*
                 * we have not read any bytes from this new chunk
                 */
                totalbytesread = 0;
            }

            int bytesToRead = chunksize - totalbytesread;

            if (len >= bytesToRead) {

                /*
                 * No need to buffer, if the caller has given a big buffer.
                 */
                rc = streamInput.read(b, off, bytesToRead);

            } else if (len >= inputBufferSize) {
                /*
                 * No need to buffer, if the caller has given a big buffer.
                 */
                rc = streamInput.read(b, off, len);
            } else {
                if (inputBufferSize >= bytesToRead) {
                    rc = streamInput.read(readbuf, 0, bytesToRead);
                } else {
                    rc = streamInput.read(readbuf, 0, inputBufferSize);
                }

                bytesleft = rc;
                bytesread = 0;
            }

            if (rc == -1) {
                /*
                 * Network problem or the wrong length was sent by the server.
                 */
                eof = true;
                throw new IOException("unexpected end of stream");
            }

            totalbytesread += rc;

            if (bytesleft == 0) {
                /*
                 * The data was read directly into the caller's buffer.
                 */
                return rc;
            }
        }

        rc = readFromBuffer(b, off, len);

        return rc;
    
private intreadChunkSize()
Read the chunk size from the input. It is a hex length followed by optional headers (ignored). and terminated with CRLF.

return
size of the buffered read

        int size = -1;

        try {
            String chunk = null;

            try {
                chunk = readLine(streamInput);
            } catch (IOException ioe) {
                /* throw new IOException(ioe.getMessage()); */
            }

            if (chunk == null) {
                throw new IOException("No Chunk Size");
            }

            int i;
            for (i = 0; i < chunk.length(); i++) {
                char ch = chunk.charAt(i);
                if (Character.digit(ch, 16) == -1)
                    break;
            }

            /* look at extensions?.... */
            size = Integer.parseInt(chunk.substring(0, i), 16);
        } catch (NumberFormatException e) {
            throw new IOException("invalid chunk size number format");
        }
        
        return size;
    
intreadChunkSizeNonBlocking()
Read a chunk size header into the readLine buffer without blocking. The stringbuffer is populated with characters one at a time. This routine is design so that a partial chunk size header could be read and then completed by a blocking read of the chunk or a subsequent call to available.

return
available data that can be read

        /*
         * Check the underlying stream to see how many bytes are
         * available. Do not read beyond the available characters,
         * because that would block.
         */
        int len = streamInput.available();
        
        /* Reset the last character from the current readLine buffer. */
        int sblen = stringbuffer.length();
        char lastchar = '\0";
        if (sblen > 0) {
            lastchar = stringbuffer.charAt(sblen - 1);
        }

        int size = -1;
        /*
         * Loop through the available characters until a full 
         * chunk size header is in the readLine buffer.
         */
        for (; len > 0; len--) {
            char c = (char) streamInput.read();
            
            if (lastchar == '\r" && c == '\n") {
                // remove the '\r' from the buffer
                stringbuffer.setLength(stringbuffer.length() - 1);

                if (stringbuffer.length() > 0) {
                    // this is a size, not the CRLF at the end of a chunk
                    try {
                        String temp = stringbuffer.toString();
                        int semi = temp.indexOf(';");

                        // skip extensions
                        if (semi > 0) {
                            temp = temp.substring(0, semi);
                        }

                        /*
                         * Reset the string buffer length so readline() will
                         * not parse this line.
                         */
                        stringbuffer.setLength(0);

                        size = Integer.parseInt(temp, 16);
                    } catch (NumberFormatException nfe) {
                        throw new IOException(
                            "invalid chunk size number format");
                    }
                    break;
                }
            } else {
                stringbuffer.append(c);
                lastchar = c;
            }
        }
        
        if (size < 0) {
            // did not get the size
            return 0;
        }

        /*
         * Update the chunksize and the total bytes that have been
         * read from the chunk. This will trigger the next call to
         * readBytes to refill the buffers as needed.
         */
        chunksize = size;
        if (size == 0) {
            eof = true;
            return 0;
        }
        
        totalbytesread = 0;

        /*
         * If the full chunk is available, return chunksize,
         * otherwise return the remainder of the available
         * bytes (e.g. partial chunk).
         */
        return (chunksize < len ? chunksize : len);
        
    
private intreadFromBuffer(byte[] b, int off, int len)
Reads up to len bytes of data from the internal buffer into an array of bytes.

param
b the buffer into which the data is read.
param
off the start offset in array b at which the data is written.
param
len the maximum number of bytes to read.
return
the total number of bytes read into the buffer, or -1 if there is no more data because the end of the stream has been reached.
exception
IOException if an I/O error occurs.

        
        /*
         * copy http buffer data into user buffer, then
         * increment and decrement counters
         */
        
        int rc;

        if (len > bytesleft) {
            rc = bytesleft;
        } else {
            rc = len;            
        }

        System.arraycopy(readbuf, bytesread, b, off, rc);

        bytesleft -= rc;
        bytesread += rc;
        
        return rc;
    
private voidreadHeaders(java.io.InputStream in)
Read the response message headers. Parse the response headers name value pairs for easy application use.

param
in input stream where the response headers are read
exception
IOException is thrown if the response headers cannot be parsed

        String line;
        String key = null;
        int prevPropIndex = headerFields.size() - 1;
        boolean firstLine = true;
        String value;
        String prevValue = null;
        int index;

        /*
         * Initialize and set the current input stream variables
         */
        bytesleft = 0;
        chunksize = -1;
        bytesread = 0;
        totalbytesread = 0;
        chunkedIn = false;
        eof = false;

        
        for (;;) {
            try {
                line = readLine(in);
            } catch (IOException ioe) {
                throw new IOException(ioe.getMessage());
            }

            if (line == null || line.equals(""))
                break;

            if ((!firstLine) && (line.charAt(0) == ' " ||
                                    line.charAt(0) == '\t")) {
                // This line is a contiuation of the previous line.

                /*
                 * The continuation is for the user readablility so restore
                 * the CR LF when appending.
                 */
                value = prevValue + "\r\n" + line;

                /*
                 * Set value by index, since there can be multiple properties
                 * with the same key.
                 */
                headerFields.setPropertyAt(prevPropIndex, value);
                prevValue = value;
                continue;
            }
                
            index = line.indexOf(':");
            if (index < 0) {
                throw new IOException("malformed header field " + line);
            }

            key = line.substring(0, index).toLowerCase();
            if (key.length() == 0) {
                throw new IOException("malformed header field, no key "+ line);
            }

            if (line.length() <= index + 1) 
                value = "";
            else 
                value = line.substring(index + 1).trim();

            /**
             * Check the response header to see if the server would like
             * to close the connection.
             * BUG#4492849
             */
            if ((key.equals("connection")) &&
                    (value.equals("close"))) {
                ConnectionCloseFlag = true;
            }

	    /*
	     * Determine if this is a chunked data transfer.
	     */
            if ((key.equals("transfer-encoding")) &&
		(value.regionMatches(true, 0, "chunked",
					 0, value.length()))) {
                chunkedIn = true;
            }

	    /*
	     * Update the Content-Length based on the header value.
	     */
            if (key.equals("content-length")) {
		try {
		    contentLength =  Integer.parseInt(value);
		} catch (IllegalArgumentException iae) {
		    // fall through
		} catch (NullPointerException npe) {
		    // fall through
		}
            }

	    /* Save the response key value pairs. */
            headerFields.addProperty(key, value);
            firstLine = false;
            prevPropIndex++;
            prevValue = value;
        }

	/* Initialize the amount of data expected. */
        if (chunkedIn) {
            chunksize = readChunkSize();
        } else {
            // do not let the read block if there is no data.
            if (method.equals(HEAD)) {
                chunksize = 0;
            } else {
                // treat non chunked data of known length as one big chunk
                chunksize = contentLength;
            }
        }

	/* Last chunk or zero length response data. */
        if (chunksize == 0) {
            eof = true;
        }
    
private java.lang.StringreadLine(java.io.InputStream in)
Uses the shared stringbuffer to read a line terminated by CRLF and return it as string. Blocks until the line is done or end of stream.

param
in InputStream to read the data
return
one line of input header or null if end of stream
exception
IOException if error encountered while reading headers

        int c;

        try {
            for (;;) {
                c = in.read();
                if (c < 0) {
                    return null;
                }

                if (c == '\r") {
                    continue;
                }

                if (c == '\n") {
                    break;
                }

                stringbuffer.append((char)c);
            }

            /* Return a whole line and reset the string buffer. */
            String line =  stringbuffer.toString(); 

            return line;
        } finally {
            stringbuffer.setLength(0);
        }
    
private voidreadResponseMessage(java.io.InputStream in)
Check the initial response message looking for the appropriate HTTP version string. Parse the response code for easy application branching on condition codes.

param
in input stream where the response headers are read
exception
IOException is thrown if the header response can not be parsed


        String line = null;
        responseCode = -1;
        responseMsg = null;

        line = readLine(in);

        /*
         * REFERENCE: HTTP1.1 document 
         * SECTION: 3.6.1 Chunked Transfer Coding
         * in some cases there may be an OPTIONAL trailer containing 
         * entity-header fields. since we don't support the available()
         * method for inputstreams and for performance reasons we
         * do not attempt to clean up the previous connections input
         * stream. the first thing we do here is read the stream and 
         * discard it.
         */
        if (line != null && line.length() == 0) {
            line = readLine(in);
        }
            
        int httpEnd, codeEnd;
        
        responseCode = -1;
        responseMsg = null;

        if (line == null) {
            throw new IOException("response empty");
        }

        httpEnd = line.indexOf(' ");
        if (httpEnd < 0) {
            if (line.length() > 10) {
                // only put the first 10 chars in the exception
                line = line.substring(0, 10);
            }

            throw new IOException("cannot find status code in response: " +
                line);
        }
    
        String temp = line.substring(0, httpEnd);
        if (!temp.startsWith("HTTP")) {
            if (httpEnd > 10) {
                // only put the first 10 chars in the exception
                temp = temp.substring(0, 10);
            }

            throw new IOException("response does not start with HTTP " +
                 "it starts with: " + temp);
        }

        httpVer = temp;
        if (line.length() <= httpEnd) {
            throw new IOException("status line ends after HTTP version");
        }
    
        codeEnd = line.substring(httpEnd + 1).indexOf(' ");
        if (codeEnd < 0) {
            throw new IOException("cannot find reason phrase in response");
        }

        codeEnd += (httpEnd + 1);
        if (line.length() <= codeEnd) {
            throw new IOException("status line end after status code");
        }
    
        try {
            responseCode = Integer.parseInt(line.substring(httpEnd+1, 
                                                               codeEnd));
        } catch (NumberFormatException nfe) {
            throw new IOException("status code in response is not a number");
        }
    
        responseMsg = line.substring(codeEnd + 1);
    
protected voidsendRequest()
If not connected, connect to the underlying socket transport and send the HTTP request and get the response header.

If an http_proxy was specified the socket connection will be made to the proxy server and the requested URL will include the full http URL.

On output the Content-Length header is included in the request based on the size of the buffered output array.

This routine inserts the Host header needed for HTTP 1.1 virtual host addressing.

This routine also receives the reply response and parses the headers for easier access. After the headers are parsed the application has access to the raw data from the socket stream connection.

exception
IOException is thrown if the connection cannot be opened

        sendRequest(false, true);
    
private voidsendRequest(boolean chunkData, boolean readResponseHeader)
If not connected, connect to the underlying socket transport and send the HTTP request and get the response header.

If an http_proxy was specified the socket connection will be made to the proxy server and the requested URL will include the full http URL.

On output the Content-Length header is included in the request based on the size of the buffered output array.

This routine inserts the Host header needed for HTTP 1.1 virtual host addressing.

This routine also receives the reply response and parses the headers for easier access. After the headers are parsed the application has access to the raw data from the socket stream connection.

param
chunkData if true chunk data sent to the server
param
readResponseHeader if true, read the response header
exception
IOException is thrown if the connection cannot be opened

        int bytesToRetry;

        if (sendingRequest || requestFinished) {
            return;
        }

        sendingRequest = true;

        try {
            if (chunkData) {
                chunkedOut = true;
            }


            bytesToRetry = bytesToWrite;

            try {
                startRequest();
                sendRequestBody();

                if (readResponseHeader) {
                    finishRequestGetResponseHeader();
                }
            } catch (IOException ioe) {
                if (!(streamConnection instanceof StreamConnectionElement)) {
                    /*
                     * This was a connection opened during this transaction.
                     * So do not try to recover.
                     */
                    throw ioe;
                }

                try {
                    connectionPool.remove(
                        (StreamConnectionElement)streamConnection);
                } catch (Exception e) {
                    // do not over throw the previous exception
                }

                if (firstChunkSent) {
                    // can't retry since we do not have the previous chunk
                    throw new IOException("Persistent connection dropped " +
                        "after first chunk sent, cannot retry");
                }

                streamConnection = null;
                streamInput = null;
                streamOutput = null;
                bytesToWrite = bytesToRetry;

                startRequest();
                sendRequestBody();

                if (readResponseHeader) {
                    finishRequestGetResponseHeader();
                }
            }

            if (chunkedOut) {
                firstChunkSent = true;
            }
        } finally {
            sendingRequest = false;
        }   
    
protected voidsendRequestBody()
Write the http request body bytes to the output stream.

exception
IOException


        int start;
        int endOfData;
        int length;

        if ((writebuf == null) || (bytesToWrite == 0)) {
            return;
        }

        start = HTTP_OUTPUT_DATA_OFFSET;
        endOfData = HTTP_OUTPUT_DATA_OFFSET + bytesToWrite;
        length = bytesToWrite;

        /*
         * If a CHUNKed session then write out the chunk size first 
         * with a trailing CRLF.
         *
         * reference: RFC2616 - section 3 protocol parameters
         * 3.6 transfer coding:
         * 3.6.1 chunked transfer coding:
         * chunk-body = chunk / last chunk / trailer / CRLF
         *       * chunk =      chunk-size / chunk-data / CRLF
         *         last_chunk = "0" / CRLF
         *         trailer    = " " / CRLF
         * *indicates its done here.
         */
        if (chunkedOut) {
            /*
             * For CHUNKed write out the chunk size with CRLF.
             * Put this before the data in write buffer.
             */
            String temp = Integer.toHexString(bytesToWrite);
            int tempLen = temp.length();

            writebuf[--start] = (byte)'\n";
            writebuf[--start] = (byte)'\r";
            for (int i = tempLen - 1; i >= 0; i--) {
                writebuf[--start] = (byte)temp.charAt(i);
            }

            length += tempLen + 2;

            /*
             * If a CHUNKed session then write out another CRLF and flush().
             * Put this after the data in the write bufffer.
             */
            writebuf[endOfData++] = (byte)'\r";
            writebuf[endOfData++] = (byte)'\n";
            length += 2;
        }           

        streamOutput.write(writebuf, start, length);
        bytesToWrite = 0;
    
private voidsendRequestHeader()
Simplifies the sendRequest() method header functionality into one method this is extremely helpful for persistent connection support and retries.

exception
IOException is thrown if the connection cannot be opened

        StringBuffer reqLine;
        String filename;
        int numberOfKeys;

        // HTTP 1.0 requests must contain content length for proxies
        if (getRequestProperty("Content-Length") == null) {
            setRequestField("Content-Length", Integer.toString(bytesToWrite));
        }

        reqLine = new StringBuffer(256);
        
        /*
         * HTTP RFC and bug#4402149,
         * if there is no path then add a slash ("/").
         */
        filename = url.path;
        if (filename == null) {
            filename = "/";
        }

        /*
         * Note: the "ref" or fragment, is not sent to the server.
         */
        reqLine.append(method);
        reqLine.append(" ");

        /*
         * Since we now use a tunnel instead of a proxy, we do not have
         * to send an absolute URI. The difference is that a proxy will
         * strip scheme and authority from the URI and a tunnel will not.
         *
         * For HTTPS purposes we will use the relative URI.
         *
         * Some HTTPS server's do not like to see "https" as the scheme of an 
         * URI and only recognize "http".
         * examples: www.wellsfargo.com sends back html with not HTTP headers,
         * e-banking.abbeynational.co.uk sends back a 404 to all requests.
         *
         * It is better to not use the absolute URL, than to hardcode the
         * the scheme to "http" all the time since that did not work with
         * e-banking.abbeynational.co.uk.
         *
         * if (http_proxy != null) {
         *     reqLine.append(protocol);
         *     reqLine.append("://");
         *     reqLine.append(url.authority);
         * }
         */

        reqLine.append(filename);

        if (url.query != null) {
            reqLine.append("?");
            reqLine.append(url.query);
        }

        reqLine.append(" ");
        reqLine.append(HTTP_VERSION);
        reqLine.append("\r\n");

        /*
         * HTTP 1/1 requests require the Host header to distinguish
         * virtual host locations.
         */
        
        setRequestField("Host", url.authority);

        if (chunkedOut) {
            /*
             * Signal the server that the body is chunked
             * by setting the Transfer-Encoding property to "chunked".
             */
            setRequestField("Transfer-Encoding", "chunked");
        }

        /*
         * Setup the various http header field/values defined and/or
         * required.
         */
        numberOfKeys = reqProperties.size();
        for (int i = 0; i < numberOfKeys; i++) {
            String key = (String)reqProperties.getKeyAt(i);
            
            if (key.equals("Content-Length")) {
                /*
                 * If its CHUNK data - no content-length: size required.
                 */
                if (chunkedOut) {
                    continue;                         
                } else {
                    /*
                     * Check that the output stream has been opened.
                     */
                    if (writebuf == null) {
                        reqLine.append("Content-Length: 0");
                    } else {
                        reqLine.append("Content-Length: ");
                        reqLine.append(bytesToWrite);
                    }

                    reqLine.append("\r\n");
                }
            } else {
                reqLine.append(key);
                reqLine.append(": ");
                reqLine.append(reqProperties.getValueAt(i));
                reqLine.append("\r\n");
            }
        }
        
        reqLine.append("\r\n");

        streamOutput.write(reqLine.toString().getBytes());
    
protected voidsetRequestField(java.lang.String key, java.lang.String value)
Add the named field to the list of request fields. This method is where a subclass should override properties.

param
key key for the request header field.
param
value the value for the request header field.


        /* https handles the proxy fields in a different way */
        if (key.startsWith("Proxy-")) {
            proxyHeaders.setProperty(key, value);
            return;
        }

        /*
         * if application setRequestProperties("Connection", "close")
         * then we need to know this & take appropriate default close action
         */
        if ((key.equals("Connection")) && (value.equals("close"))) {
            ConnectionCloseFlag = true;
        }

        if ((key.equals("Transfer-Encoding")) && (value.equals("chunked"))) {
            chunkedOut = true;
        }

        reqProperties.setProperty(key, value);
    
public voidsetRequestMethod(java.lang.String method)
Set the request method of the current connection.

param
method request method is GET, HEAD or POST
exception
IOException is thrown if the connection is already open
see
#getRequestMethod

        ensureOpen();

        
        if (streamConnection != null) {
            throw new IOException("connection already open");
        }

        /* 
         * The request method can not be changed once the output
         * stream has been opened.
         */
        if (maxOStreams == 0) {
            return;
        }
        
        if (!method.equals(HEAD) && 
            !method.equals(GET) && 
            !method.equals(POST)) {
            throw new IOException("unsupported method: " + method);
        }

        this.method = method;
    
public voidsetRequestProperty(java.lang.String key, java.lang.String value)
Set the request header name/value of specific HTTP 1.1 header field.

param
key property name
param
value property value
exception
IllegalArgumentException is thrown if value contains CRLF.
exception
IOException If some other kind of I/O error occurs.
see
#getRequestProperty

        int index = 0;

        ensureOpen();
        
        if (streamConnection != null) {
            throw new IOException("connection already open");
        }

        /* 
         * The request headers can not be changed once the output 
         * stream has been opened.
         */
        if (maxOStreams == 0) {
            return;
        } 

        // Look to see if a hacker embedded any extra fields.
        for (; ; ) {
            index = value.indexOf("\r\n", index);

            if (index == -1) {
                break;
            }

            // Allow legal header value continuations. CRLF + (SP|HT)
            index += 2;

            if (index >= value.length() || (value.charAt(index) != ' " &&
                   value.charAt(index) != '\t")) {
                // illegal values passed for properties - raise an exception
                throw new IllegalArgumentException("illegal value found");
            }
        }

        setRequestField(key, value);
    
private voidskipEndOfChunkCRLF()
Skips the CRLF at the end of each chunk in the InputStream.

exception
IOException if the LF half of the ending CRLF is missing.

 
        int ch;

        if (stringbuffer.length() > 1) {
            /*
             * readChunkSizeNonBlocking does not leave CRLF in the buffer
             * so assume that the ending CRLF has been skipped already
             */
            return;
        }

        // readChunkSizeNonBlocking could have left a \r single in the buffer
        if (stringbuffer.length() == 1) {
            if (stringbuffer.charAt(0) != '\r") {
                // assume that the ending CRLF has been skipped already
                return;
            }
             
            // remove the '\r'
            stringbuffer.setLength(0);
            ch = streamInput.read();
            if (ch != '\n") {
                throw new IOException("missing the LF of an expected CRLF");
            }

            return;
        }
                
        ch = streamInput.read();
        if (ch != '\r") {
            /*
             * assume readChunkSizeNonBlocking has read the end of the chunk
             * and that this is the next chunk size, so put the char in the
             * buffer for readChunkSize and return
             */
            stringbuffer.append(ch);
            return;
        }

        ch = streamInput.read();
        if (ch != '\n") {
            throw new IOException("missing the LF of an expected CRLF");
        }
    
private voidstartRequest()
If not connected, connect to the underlying socket transport and send the HTTP request headers.

If an http_proxy was specified the socket connection will be made to the proxy server and the requested URL will include the full http URL.

On output the Content-Length header is included in the request based on the size of the buffered output array.

This routine inserts the Host header needed for HTTP 1.1 virtual host addressing.

This routine also receives the reply response and parses the headers for easier access. After the headers are parsed the application has access to the raw data from the socket stream connection.

exception
IOException is thrown if the connection cannot be opened

        if (streamConnection != null) {
            return;
        }

        streamConnect();
        sendRequestHeader();
    
private voidstreamConnect()
Find a previous connection in the pool or try to connect to the underlying stream transport.

exception
IOException is thrown if the connection cannot be opened

        verifyPermissionCheck();

        streamConnection = connectionPool.get(protocol, url.host, url.port);

        if (streamConnection == null) {
            streamConnection = connect();
        }

        /*
         * Because StreamConnection.open*Stream cannot be called twice
         * the HTTP connect method may have already open the streams
         * to connect to the proxy and saved them in the field variables
         * already.
         */
        if (streamOutput != null) {
            return;
        }

        streamOutput = streamConnection.openDataOutputStream();
        streamInput = streamConnection.openDataInputStream();
    
protected intwriteBytes(byte[] b, int off, int len)
Writes len bytes from the specified byte array starting at offset off to this output stream.

This method can only be called after an OutputStream setup has be done.

param
b the data.
param
off the start offset in the data.
param
len the number of bytes to write.
return
int number of bytes written to stream
exception
IOException if an I/O error occurs. In particular, an IOException is thrown if the output stream is closed.

        int bytesToCopy;

        if (requestFinished) {
            throw new IllegalStateException(
                "Write attempted after request finished");
        }

        if (bytesToWrite == outputDataSize) {
            /*
             * Send the bytes in the write buffer as a chunk to the server
             * so more bytes can be put in the buffer.
             */
            sendRequest(true, false);
        }

        /*
         * Our parent class will call this method in a loop until all the bytes
         * are written. So this method does not have to process all the bytes
         * in one call.
         */
        bytesToCopy = outputDataSize - bytesToWrite;
        if (len < bytesToCopy) {
            bytesToCopy = len;
        }
            
        System.arraycopy(b, off, writebuf,
            HTTP_OUTPUT_DATA_OFFSET + bytesToWrite, bytesToCopy);
        bytesToWrite += bytesToCopy;

        return bytesToCopy;