rfc2616
对于返回是chunked格式的字节流的处理
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
* http response chunked transfer type input stream
* @author jeffrey.ship
*
*/
public class ChunkedInputStream {
public static final int CR = 13; // <US-ASCII CR, carriage return (13)>
public static final int LF = 10; // <US-ASCII LF, linefeed (10)>
public static final int SP = 32; // <US-ASCII SP, space (32)>
public static final int HT = 9; // <US-ASCII HT, horizontal-tab (9)>
private InputStream is;
private StringBuffer stringbuffer;// share buffer
private Map<String, String> headerFields;
private int responseCode;
private String responseMsg;
private int bytesleft; // Number of bytes left in current chunk
private int bytesread; // Number of bytes read since the stream was opened
private boolean chunked; // True if Transfer-Encoding: chunked
private boolean eof; // True if EOF seen
public ChunkedInputStream(InputStream is) {
this.is = is;
headerFields = new HashMap<String, String>();
stringbuffer = new StringBuffer(32);
}
public void init() throws Exception {
readResponseMessage(is);
readHeaders(is);
// Determine if this is a chunked datatransfer and setup
String te = (String) headerFields.get("transfer-encoding");
if (te != null && te.equals("chunked")) {
chunked = true;
bytesleft = readChunkSize();
eof = (bytesleft == 0);
}
}
private void readResponseMessage(InputStream in) throws IOException {
String line = readLine(in);
int httpEnd, codeEnd;
responseCode = -1;
responseMsg = null;
malformed: {
if (line == null)
break malformed;
httpEnd = line.indexOf(' ');
if (httpEnd < 0)
break malformed;
String httpVer = line.substring(0, httpEnd);
if (!httpVer.startsWith("HTTP"))
break malformed;
if (line.length() <= httpEnd)
break malformed;
codeEnd = line.substring(httpEnd + 1).indexOf(' ');
if (codeEnd < 0)
break malformed;
codeEnd += (httpEnd + 1);
if (line.length() <= codeEnd)
break malformed;
try {
responseCode = Integer.parseInt(line.substring(httpEnd + 1, codeEnd));
} catch (NumberFormatException nfe) {
break malformed;
}
responseMsg = line.substring(codeEnd + 1);
return;
}
throw new IOException("malformed response message");
}
public int getResponseCode() {
return responseCode;
}
public String getResponseMsg() {
return responseMsg;
}
private void readHeaders(InputStream in) throws IOException {
String line, key, value;
int index;
for (;;) {
line = readLine(in);
if (line == null || line.equals(""))
return;
index = line.indexOf(':');
if (index < 0)
throw new IOException("malformed header field");
key = line.substring(0, index);
if (key.length() == 0)
throw new IOException("malformed header field");
if (line.length() <= index + 2) {
value = "";
} else {
value = line.substring(index + 2);
}
headerFields.put(toLowerCase(key), value);
}
}
public String getResponseHeaders() {
Set<String> keys = headerFields.keySet();
StringBuffer headers = new StringBuffer();
for (String key : keys) {
String value = headerFields.get(key);
headers.append(key + ":" + value + "\r\n");
}
return headers.toString();
}
public String getHeaderField(String name) {
return (String) headerFields.get(toLowerCase(name));
}
/** */
/**
* Reads the next byte of data from the input stream. The value byte is
* returned as an <code>int</code> in the range <code>0</code> to
* <code>255</code>. If no byte is available because the end of the
* stream has been reached, the value <code>-1</code> is returned. This
* method blocks until input data is available, the end of the stream is
* detected, or an exception is thrown.
*
* <p>
* A subclass must provide an implementation of this method.
*
* @return the next byte of data, or <code>-1</code> if the end of the
* stream is reached.
* @exception IOException
* if an I/O error occurs.
*/
public int read() throws IOException {
// Be consistent about returning EOF once encountered.
if (eof) {
return -1;
}
/**//*
* If all the current chunk has been read and this is a chunked
* transfer then read the next chunk length.
*/
if (bytesleft <= 0 && chunked) {
readCRLF(); // Skip trailing
bytesleft = readChunkSize();
if (bytesleft == 0) {
eof = true;
return -1;
}
}
int ch = is.read();
eof = (ch == -1);
bytesleft--;
bytesread++;
return ch;
}
/** */
/**
* Reads some number of bytes from the input stream and stores them into the
* buffer array <code>b</code>. The number of bytes actually read is
* returned as an integer. This method blocks until input data is available,
* end of file is detected, or an exception is thrown. (For HTTP requests
* where the content length has been specified in the response headers, the
* size of the read may be reduced if there are fewer bytes left than the
* size of the supplied input buffer.)
*
* @param b
* the buffer into which the data is read.
* @return the total number of bytes read into the buffer, or
* <code>-1</code> is there is no more data because the end of the
* stream has been reached.
* @exception IOException
* if an I/O error occurs.
* @see java.io.InputStream#read(byte[])
*/
public int read(byte[] b) throws IOException {
long len = getLength();
if (len != -1) {
// More bytes are expected
len -= bytesread;
} else {
// Buffered reading in chunks
len = b.length;
}
if (len == 0) {
eof = true;
// All expected bytes have been read
return -1;
}
return read(b, 0, (int) (len < b.length ? len : b.length));
}
public int read(byte b[], int off, int len) throws IOException {
if (b == null) {
throw new NullPointerException();
} else if (off < 0 || len < 0 || len > b.length - off) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return 0;
}
int c = read();
if (c == -1) {
return -1;
}
b[off] = (byte) c;
int i = 1;
try {
for (; i < len; i++) {
c = read();
if (c == -1) {
break;
}
b[off + i] = (byte) c;
}
} catch (IOException ee) {
}
return i;
}
public long getLength() {
return getHeaderFieldInt("content-length", -1);
}
public int getHeaderFieldInt(String name, int def) {
try {
return Integer.parseInt(getHeaderField(name));
} catch (Throwable t) {
}
return def;
}
/**
* 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.
*
* This method simply returns the number of bytes left from a chunked
* response from an HTTP 1.1 server.
*/
public int available() throws IOException {
return bytesleft;
}
/**//*
* Read <cr><lf> from the InputStream. @exception IOException is thrown
* if either <CR> or <LF> is missing.
*/
private void readCRLF() throws IOException {
int ch;
ch = is.read();
if (ch != CR) {
throw new IOException("missing CRLF");
}
ch = is.read();
if (ch != LF) {
throw new IOException("missing CRLF");
}
}
public void close() throws IOException {
is.close();
}
private String toLowerCase(String string) {
// Uses the shared stringbuffer to create a lower case string.
stringbuffer.setLength(0);
for (int i = 0; i < string.length(); i++) {
stringbuffer.append(Character.toLowerCase(string.charAt(i)));
}
return stringbuffer.toString();
}
/**//*
* Uses the shared stringbuffer to read a line terminated by <cr><lf>
* and return it as string.
*/
private String readLine(InputStream in) {
int c;
StringBuffer stringbuffer = new StringBuffer();
stringbuffer.setLength(0);
for (;;) {
try {
c = in.read();
if (c < 0) {
return null;
}
} catch (IOException ioe) {
return null;
}
if (isEnd(c)) {
try {
// LF
in.read();
} catch (IOException e) {
e.printStackTrace();
}
break;
}
stringbuffer.append((char) c);
}
return stringbuffer.toString();
}
public static boolean isEnd(int ch) {
return ch == CR || ch == LF;
}
/**//*
* Read the chunk size from the input. It is a hex length followed by
* optional headers (ignored) and terminated with <cr><lf>.
*/
private int readChunkSize() throws IOException {
int size = -1;
try {
String chunk = readLine(is);
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("Bogus chunk size");
}
return size;
}
}