Fields Summary |
---|
protected static final String | HTTP_VERSIONHTTP version string to use with all outgoing HTTP requests. |
private static final int | HTTP_OUTPUT_DATA_OFFSETWhere to start the data in the output buffer. |
private static final int | HTTP_OUTPUT_EXTRA_ROOMHow must extra room for the chunk terminator. |
private static com.sun.midp.security.SecurityToken | classSecurityTokenThis class has a different security domain than the MIDlet suite |
private static int | inputBufferSizeDefault size for input buffer. |
private static int | outputBufferSizeDefault size for output buffer. |
private static int | outputDataSizeHow much data can be put in the output buffer. |
private static String | http_proxyThe "host:port" value to use for HTTP proxied requests. |
private static int | maxNumberOfPersistentConnectionsMaximum number of persistent connections. |
private static long | connectionLingerTimeConnection linger time in the pool, default 60 seconds. |
private static StreamConnectionPool | connectionPoolPersistent connection pool. |
private static boolean | nonPersistentFlagTrue if com.sun.midp.io.http.force_non_persistent = true. |
protected String | saved_urlSaved copy of URL string to be processed. |
protected int | default_portDefault port number for this protocol. |
protected com.sun.midp.io.HttpUrl | urlParsed Url. |
protected String | hostAndPorturl.host + ":" + url.port. |
protected int | responseCodeNumeric code returned from HTTP response header. |
protected String | responseMsgMessage string from HTTP response header. |
protected com.sun.midp.io.Properties | reqPropertiesCollection of request headers as name/value pairs. |
protected com.sun.midp.io.Properties | headerFieldsCollection of response headers as name/value pairs. |
protected String | methodHTTP method type for the current request. |
private javax.microedition.io.StreamConnection | streamConnectionLow level socket connection used for the HTTP requests. |
private DataOutputStream | streamOutputLow level socket output stream. |
private DataInputStream | streamInputLow level socket input stream. |
private StringBuffer | stringbufferA shared temporary header buffer. |
private String | httpVerHTTP version string set with all incoming HTTP responses. |
private boolean | ConnectionCloseFlagUsed when appl calls setRequestProperty("Connection", "close"). |
private int | contentLengthContent-Length from response header, or -1 if missing. |
private int | chunksizeTotal number of bytes in the current chunk or content-length when
data is sent as one big chunk. |
private int | totalbytesreadNumber of bytes read from the stream for non-chunked data or
the bytes read from the current chunk. |
private boolean | chunkedInTrue if Transfer-Encoding: chunkedIn. |
private boolean | chunkedOutTrue if Transfer-Encoding: chunkedOut. |
private boolean | firstChunkSentTrue after the first chunk has been sent. |
private boolean | sendingRequestTrue if the request is being sent. |
private boolean | requestFinishedTrue if the entire request has been sent to the server. |
private boolean | eofTrue if eof seen. |
private byte[] | readbufInternal stream buffer to minimize the number of TCP socket reads. |
private int | bytesleftNumber of bytes left in internal input stream buffer. |
private int | bytesreadNumber of bytes read from the internal input stream buffer. |
private byte[] | writebufBuffered data output for content length calculation. |
private int | bytesToWriteNumber of bytes of data that need to be written from the buffer. |
private com.sun.midp.io.Properties | proxyHeadersCollection of "Proxy-" headers as name/value pairs. |
private byte | handshakeErrorLast handshake error. |
private boolean | readInProgressHolds 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. |
Methods Summary |
---|
public int | available()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.
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 void | closeOutputStream()Close the OutputStream and transition to connected state.
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 void | connect(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.
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.StreamConnection | connect()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.
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 void | disconnect()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 void | disconnect(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.
try {
if (connection != null) {
connection.close();
}
} finally {
try {
if (outputStream != null) {
outputStream.close();
}
} finally {
if (inputStream != null) {
inputStream.close();
}
}
}
|
protected void | doTunnelHandshake(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".
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 void | finishRequestGetResponseHeader()Finish the http request and reads the response headers.
// 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 void | flush()If any output data, turn on chunking send it to the server.
if (requestFinished) {
throw new IllegalStateException(
"Flush attempted after request finished");
}
if (bytesToWrite > 0) {
sendRequest(true, false);
}
|
public long | getDate()Get the Date header for the current response.
return getHeaderFieldDate("date", 0);
|
public java.lang.String | getEncoding()Get the Content-Encoding for the current response.
try {
return getHeaderField("content-encoding");
} catch (IOException x) {
return null;
}
|
public long | getExpiration()Get the Expires header for the current response.
return getHeaderFieldDate("expires", 0);
|
public java.lang.String | getFile()Get the file path name parsed from the URL.
return url.path;
|
public java.lang.String | getHeaderField(java.lang.String name)Get the named header field for the current response.
ensureOpen();
sendRequest();
return (headerFields.getProperty(name.toLowerCase()));
|
public java.lang.String | getHeaderField(int index)Get the indexed header field for the current response.
ensureOpen();
sendRequest();
if (index >= headerFields.size()) {
return null;
}
return (headerFields.getValueAt(index));
|
public long | getHeaderFieldDate(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.
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 int | getHeaderFieldInt(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.
ensureOpen();
sendRequest();
try {
return Integer.parseInt(getHeaderField(name));
} catch (IllegalArgumentException iae) {
// fall through
} catch (NullPointerException npe) {
// fall through
}
return def;
|
public java.lang.String | getHeaderFieldKey(int index)Get the indexed header field value for the current response.
ensureOpen();
sendRequest();
if (index >= headerFields.size())
return null;
return ((String)(headerFields.getKeyAt(index)));
|
public java.lang.String | getHost()Get the host name parsed from the URL.
return url.host;
|
public long | getLastModified()Get the Last-Modified date header for the current response.
return getHeaderFieldDate("last-modified", 0);
|
public long | getLength()Get the Content-Length for the current response.
try {
ensureOpen();
sendRequest();
} catch (IOException ioe) {
// Fall through to return -1 for length
}
return contentLength;
|
public int | getPort()Get the query string parsed from the URL.
return url.port;
|
public java.lang.String | getProtocol()Get the protocol scheme parsed from the URL.
return protocol;
|
public java.lang.String | getQuery()Get the query string parsed from the URL.
return url.query;
|
public java.lang.String | getRef()Get the fragment identifier parsed from the URL.
return url.fragment;
|
public java.lang.String | getRequestMethod()Get the request method of the current connection.
return method;
|
public java.lang.String | getRequestProperty(java.lang.String key)Get the request header value for the named property.
/* https handles the proxy fields in a different way */
if (key.startsWith("Proxy-")) {
return proxyHeaders.getProperty(key);
}
return reqProperties.getProperty(key);
|
public int | getResponseCode()Get the response code of the current request.
ensureOpen();
sendRequest();
return responseCode;
|
public java.lang.String | getResponseMessage()Get the response message of the current request.
ensureOpen();
sendRequest();
return responseMsg;
|
protected javax.microedition.io.StreamConnection | getStreamConnection()Gets the underlying stream connection.
return streamConnection;
|
public java.lang.String | getType()Get the Content-Type for the current response.
try {
return getHeaderField("content-type");
} catch (IOException x) {
return null;
}
|
public java.lang.String | getURL()Get the original URL used to open the HTTP connection.
/*
* RFC: Add back protocol stripped by Content Connection.
*/
return protocol + ":" + saved_url;
|
public static void | initSecurityToken(com.sun.midp.security.SecurityToken token)Initializes the security token for this class, so it can
perform actions that a normal MIDlet Suite cannot.
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.InputStream | openInputStream()Open the input stream if it has not already been opened.
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.OutputStream | openOutputStream()Open the output stream if it has not already been opened.
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 int | readBytes(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.
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 int | readBytesChunked(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.
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 int | readChunkSize()Read the chunk size from the input.
It is a hex length followed by optional headers (ignored).
and terminated with CRLF.
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;
|
int | readChunkSizeNonBlocking()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.
/*
* 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 int | readFromBuffer(byte[] b, int off, int len)Reads up to len bytes of data from the internal buffer into
an array of bytes.
/*
* 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 void | readHeaders(java.io.InputStream in)Read the response message headers.
Parse the response headers name value pairs for easy application use.
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.String | readLine(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.
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 void | readResponseMessage(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.
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 void | sendRequest()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.
sendRequest(false, true);
|
private void | sendRequest(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.
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 void | sendRequestBody()Write the http request body bytes to the output stream.
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 void | sendRequestHeader()Simplifies the sendRequest() method header functionality into one method
this is extremely helpful for persistent connection support and
retries.
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 void | setRequestField(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.
/* 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 void | setRequestMethod(java.lang.String method)Set the request method of the current connection.
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 void | setRequestProperty(java.lang.String key, java.lang.String value)Set the request header name/value of specific HTTP 1.1 header field.
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 void | skipEndOfChunkCRLF()Skips the CRLF at the end of each chunk in the InputStream.
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 void | startRequest()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.
if (streamConnection != null) {
return;
}
streamConnect();
sendRequestHeader();
|
private void | streamConnect()Find a previous connection in the pool or try to connect to the
underlying stream transport.
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 int | writeBytes(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.
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;
|