All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.aliyun.oss.common.comm.io.ChunkedUploadStream Maven / Gradle / Ivy

There is a newer version: 3.4.2
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package com.aliyun.oss.common.comm.io;

import java.io.IOException;
import java.io.InputStream;

import com.aliyun.oss.ClientException;

public class ChunkedUploadStream extends InputStream {

    private static final int DEFAULT_CHUNK_SIZE = 128 * 1024;
    private static final String DEFAULT_CHARTSET_NAME = "utf-8";
    private static final String CLRF = "\r\n";

    private InputStream innerStream;
    private byte[] inputBuffer;
    private byte[] outputBuffer;
    private int outputBufferPos = -1;
    private int outputBufferDataLen = -1;

    private final int innerStreamBufferSize;
    private boolean innerStreamConsumed = false;
    private boolean isTerminatingChunk = false;

    public ChunkedUploadStream(InputStream innerStream, int innerStreamBufferSize) {
        if (innerStream == null) {
            throw new IllegalArgumentException("Source input stream should not be null");
        }

        this.innerStream = innerStream;
        this.innerStreamBufferSize = innerStreamBufferSize;
        this.inputBuffer = new byte[DEFAULT_CHUNK_SIZE];
        this.outputBuffer = new byte[CalculateChunkHeaderLength(DEFAULT_CHUNK_SIZE)];
    }

    @Override
    public int read() throws IOException {
        byte[] tmp = new byte[1];
        int count = read(tmp);
        if (count != -1) {
            return tmp[0];
        } else {
            return count;
        }
    }

    @Override
    public int read(byte[] buffer) throws IOException {
        return read(buffer, 0, buffer.length);
    }

    @Override
    public int read(byte[] buffer, int offset, int count) throws IOException {
        if (buffer == null) {
            throw new NullPointerException();
        } else if (offset < 0 || count < 0 || count > buffer.length - offset) {
            throw new IndexOutOfBoundsException(
                    String.format("buffer size: %n, offset: %n, count: %n", buffer.length, offset, count));
        } else if (count == 0) {
            return 0;
        }

        if (outputBufferPos == -1) {
            if (innerStreamConsumed && isTerminatingChunk) {
                return -1;
            }

            int bytesRead = fillInputBuffer();
            constructOutputBufferChunk(bytesRead);
            isTerminatingChunk = (innerStreamConsumed && bytesRead == 0);
        }

        int outputRemaining = outputBufferDataLen - outputBufferPos;
        int bytesToRead = count;
        if (outputRemaining < count) {
            bytesToRead = outputRemaining;
        }

        System.arraycopy(outputBuffer, outputBufferPos, buffer, 0, bytesToRead);
        outputBufferPos += bytesToRead;
        if (outputBufferPos >= outputBufferDataLen) {
            outputBufferPos = -1;
        }

        return bytesToRead;
    }

    private int fillInputBuffer() {
        if (innerStreamConsumed) {
            return 0;
        }

        int inputBufferPos = 0;
        while (inputBufferPos < inputBuffer.length && !innerStreamConsumed) {
            int chunkBufferRemaining = inputBuffer.length - inputBufferPos;
            if (chunkBufferRemaining > innerStreamBufferSize) {
                chunkBufferRemaining = innerStreamBufferSize;
            }

            int bytesRead = 0;
            try {
                bytesRead = innerStream.read(inputBuffer, inputBufferPos, chunkBufferRemaining);
                if (bytesRead == -1) {
                    innerStreamConsumed = true;
                } else {
                    inputBufferPos += bytesRead;
                }
            } catch (IOException e) {
                throw new ClientException("Unexpected IO exception, " + e.getMessage(), e);
            }
        }

        return inputBufferPos;
    }

    private void constructOutputBufferChunk(int dataLen) {
        StringBuilder chunkHeader = new StringBuilder();
        chunkHeader.append(Integer.toHexString(dataLen));
        chunkHeader.append(CLRF);

        try {
            byte[] header = chunkHeader.toString().getBytes(DEFAULT_CHARTSET_NAME);
            byte[] trailer = CLRF.getBytes(DEFAULT_CHARTSET_NAME);

            int writePos = 0;
            System.arraycopy(header, 0, outputBuffer, writePos, header.length);
            writePos += header.length;
            if (dataLen > 0) {
                System.arraycopy(inputBuffer, 0, outputBuffer, writePos, dataLen);
                writePos += dataLen;
            }
            System.arraycopy(trailer, 0, outputBuffer, writePos, trailer.length);

            outputBufferPos = 0;
            outputBufferDataLen = header.length + dataLen + trailer.length;
        } catch (Exception e) {
            throw new ClientException("Unable to sign the chunked data, " + e.getMessage(), e);
        }
    }

    private static int CalculateChunkHeaderLength(long chunkDataSize) {
        return (int) (Long.toHexString(chunkDataSize).length() + CLRF.length() + chunkDataSize + CLRF.length());
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy