http 协议
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;
}
}
对于返回是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;
}
}