ChunkedInputStream 源码

本文深入探讨了HTTP协议中Chunked传输编码的解析过程,通过Sun内置的HttpClient类库,详细介绍了ChunkedInputStream类如何读取和处理分块数据,确保了数据的正确性和完整性。

Socket之Sun内置HttpClient解码类 ,解析HTTP协议

Transfer-Encoding:chunked

package sun.net.www.http;

import java.io.IOException;
import sun.net.www.MessageHeader;
import java.io.InputStream;

public class ChunkedInputStream extends InputStream implements Hurryable
{
    private InputStream in;
    private HttpClient hc;
    private MessageHeader responses;
    private int chunkSize;
    private int chunkRead;
    private byte[] chunkData;
    private int chunkPos;
    private int chunkCount;
    private byte[] rawData;
    private int rawPos;
    private int rawCount;
    private boolean error;
    private boolean closed;
    private static final int MAX_CHUNK_HEADER_SIZE = 2050;
    static final int STATE_AWAITING_CHUNK_HEADER = 1;
    static final int STATE_READING_CHUNK = 2;
    static final int STATE_AWAITING_CHUNK_EOL = 3;
    static final int STATE_AWAITING_TRAILERS = 4;
    static final int STATE_DONE = 5;
    private int state;
    
    private void ensureOpen() throws IOException {
        if (this.closed) {
            throw new IOException("stream is closed");
        }
    }
    
    private void ensureRawAvailable(final int n) {
        if (this.rawCount + n > this.rawData.length) {
            final int rawCount = this.rawCount - this.rawPos;
            if (rawCount + n > this.rawData.length) {
                final byte[] rawData = new byte[rawCount + n];
                if (rawCount > 0) {
                    System.arraycopy(this.rawData, this.rawPos, rawData, 0, rawCount);
                }
                this.rawData = rawData;
            }
            else if (rawCount > 0) {
                System.arraycopy(this.rawData, this.rawPos, this.rawData, 0, rawCount);
            }
            this.rawCount = rawCount;
            this.rawPos = 0;
        }
    }
    
    private void closeUnderlying() throws IOException {
        if (this.in == null) {
            return;
        }
        if (!this.error && this.state == 5) {
            this.hc.finished();
        }
        else if (!this.hurry()) {
            this.hc.closeServer();
        }
        this.in = null;
    }
    
    private int fastRead(final byte[] array, final int n, final int n2) throws IOException {
        final int n3 = this.chunkSize - this.chunkRead;
        final int n4 = (n3 < n2) ? n3 : n2;
        if (n4 <= 0) {
            return 0;
        }
        int read;
        try {
            read = this.in.read(array, n, n4);
        }
        catch (IOException ex) {
            this.error = true;
            throw ex;
        }
        if (read > 0) {
            this.chunkRead += read;
            if (this.chunkRead >= this.chunkSize) {
                this.state = 3;
            }
            return read;
        }
        this.error = true;
        throw new IOException("Premature EOF");
    }
    
    private void processRaw() throws IOException {
        while (this.state != 5) {
            switch (this.state) {
                case 1: {
                    int rawPos = this.rawPos;
                    while (rawPos < this.rawCount && this.rawData[rawPos] != 10) {
                        if (++rawPos - this.rawPos >= 2050) {
                            this.error = true;
                            throw new IOException("Chunk header too long");
                        }
                    }
                    if (rawPos >= this.rawCount) {
                        return;
                    }
                    String s;
                    int i;
                    for (s = new String(this.rawData, this.rawPos, rawPos - this.rawPos + 1, "US-ASCII"), i = 0; i < s.length(); ++i) {
                        if (Character.digit(s.charAt(i), 16) == -1) {
                            break;
                        }
                    }
                    try {
                        this.chunkSize = Integer.parseInt(s.substring(0, i), 16);
                    }
                    catch (NumberFormatException ex) {
                        this.error = true;
                        throw new IOException("Bogus chunk size");
                    }
                    this.rawPos = rawPos + 1;
                    this.chunkRead = 0;
                    if (this.chunkSize > 0) {
                        this.state = 2;
                        continue;
                    }
                    this.state = 4;
                    continue;
                }
                case 2: {
                    if (this.rawPos >= this.rawCount) {
                        return;
                    }
                    final int min = Math.min(this.chunkSize - this.chunkRead, this.rawCount - this.rawPos);
                    if (this.chunkData.length < this.chunkCount + min) {
                        final int chunkCount = this.chunkCount - this.chunkPos;
                        if (this.chunkData.length < chunkCount + min) {
                            final byte[] chunkData = new byte[chunkCount + min];
                            System.arraycopy(this.chunkData, this.chunkPos, chunkData, 0, chunkCount);
                            this.chunkData = chunkData;
                        }
                        else {
                            System.arraycopy(this.chunkData, this.chunkPos, this.chunkData, 0, chunkCount);
                        }
                        this.chunkPos = 0;
                        this.chunkCount = chunkCount;
                    }
                    System.arraycopy(this.rawData, this.rawPos, this.chunkData, this.chunkCount, min);
                    this.rawPos += min;
                    this.chunkCount += min;
                    this.chunkRead += min;
                    if (this.chunkSize - this.chunkRead <= 0) {
                        this.state = 3;
                        continue;
                    }
                }
                case 3: {
                    if (this.rawPos + 1 >= this.rawCount) {
                        return;
                    }
                    if (this.rawData[this.rawPos] != 13) {
                        this.error = true;
                        throw new IOException("missing CR");
                    }
                    if (this.rawData[this.rawPos + 1] != 10) {
                        this.error = true;
                        throw new IOException("missing LF");
                    }
                    this.rawPos += 2;
                    this.state = 1;
                    continue;
                }
                case 4: {
                    int rawPos2;
                    for (rawPos2 = this.rawPos; rawPos2 < this.rawCount && this.rawData[rawPos2] != 10; ++rawPos2) {}
                    if (rawPos2 >= this.rawCount) {
                        return;
                    }
                    if (rawPos2 == this.rawPos) {
                        this.error = true;
                        throw new IOException("LF should be proceeded by CR");
                    }
                    if (this.rawData[rawPos2 - 1] != 13) {
                        this.error = true;
                        throw new IOException("LF should be proceeded by CR");
                    }
                    if (rawPos2 == this.rawPos + 1) {
                        this.state = 5;
                        this.closeUnderlying();
                        return;
                    }
                    final String s2 = new String(this.rawData, this.rawPos, rawPos2 - this.rawPos, "US-ASCII");
                    final int index = s2.indexOf(58);
                    if (index == -1) {
                        throw new IOException("Malformed tailer - format should be key:value");
                    }
                    this.responses.add(s2.substring(0, index).trim(), s2.substring(index + 1, s2.length()).trim());
                    this.rawPos = rawPos2 + 1;
                    continue;
                }
            }
        }
    }
    
    private int readAheadNonBlocking() throws IOException {
        final int available = this.in.available();
        if (available > 0) {
            this.ensureRawAvailable(available);
            int read;
            try {
                read = this.in.read(this.rawData, this.rawCount, available);
            }
            catch (IOException ex) {
                this.error = true;
                throw ex;
            }
            if (read < 0) {
                this.error = true;
                return -1;
            }
            this.rawCount += read;
            this.processRaw();
        }
        return this.chunkCount - this.chunkPos;
    }
    
    private int readAheadBlocking() throws IOException {
        while (this.state != 5) {
            this.ensureRawAvailable(32);
            int read;
            try {
                read = this.in.read(this.rawData, this.rawCount, this.rawData.length - this.rawCount);
            }
            catch (IOException ex) {
                this.error = true;
                throw ex;
            }
            if (read < 0) {
                this.error = true;
                throw new IOException("Premature EOF");
            }
            this.rawCount += read;
            this.processRaw();
            if (this.chunkCount > 0) {
                return this.chunkCount - this.chunkPos;
            }
        }
        return -1;
    }
    
    private int readAhead(final boolean b) throws IOException {
        if (this.state == 5) {
            return -1;
        }
        if (this.chunkPos >= this.chunkCount) {
            this.chunkCount = 0;
            this.chunkPos = 0;
        }
        if (b) {
            return this.readAheadBlocking();
        }
        return this.readAheadNonBlocking();
    }
    
    public ChunkedInputStream(final InputStream in, final HttpClient hc, final MessageHeader responses) throws IOException {
        this.chunkData = new byte[4096];
        this.rawData = new byte[32];
        this.in = in;
        this.responses = responses;
        this.hc = hc;
        this.state = 1;
    }
    
    @Override
    public synchronized int read() throws IOException {
        this.ensureOpen();
        if (this.chunkPos >= this.chunkCount && this.readAhead(true) <= 0) {
            return -1;
        }
        return this.chunkData[this.chunkPos++] & 0xFF;
    }
    
    @Override
    public synchronized int read(final byte[] array, final int n, final int n2) throws IOException {
        this.ensureOpen();
        if (n < 0 || n > array.length || n2 < 0 || n + n2 > array.length || n + n2 < 0) {
            throw new IndexOutOfBoundsException();
        }
        if (n2 == 0) {
            return 0;
        }
        int ahead = this.chunkCount - this.chunkPos;
        if (ahead <= 0) {
            if (this.state == 2) {
                return this.fastRead(array, n, n2);
            }
            ahead = this.readAhead(true);
            if (ahead < 0) {
                return -1;
            }
        }
        final int n3 = (ahead < n2) ? ahead : n2;
        System.arraycopy(this.chunkData, this.chunkPos, array, n, n3);
        this.chunkPos += n3;
        return n3;
    }
    
    @Override
    public synchronized int available() throws IOException {
        this.ensureOpen();
        final int n = this.chunkCount - this.chunkPos;
        if (n > 0) {
            return n;
        }
        final int ahead = this.readAhead(false);
        if (ahead < 0) {
            return 0;
        }
        return ahead;
    }
    
    @Override
    public synchronized void close() throws IOException {
        if (this.closed) {
            return;
        }
        this.closeUnderlying();
        this.closed = true;
    }
    
    @Override
    public synchronized boolean hurry() {
        if (this.in == null || this.error) {
            return false;
        }
        try {
            this.readAhead(false);
        }
        catch (Exception ex) {
            return false;
        }
        return !this.error && this.state == 5;
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值