/*
* Copyright (c) 2010-2012 Sonatype, Inc. All rights reserved.
*
* This program is licensed to you under the Apache License Version 2.0,
* and you may not use this file except in compliance with the Apache License Version 2.0.
* You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the Apache License Version 2.0 is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the Apache License Version 2.0 for the specific language governing permissions and limitations there under.
*/
package com.ning.http.client.generators;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import com.ning.http.client.Body;
import com.ning.http.client.BodyGenerator;
/**
* A {@link BodyGenerator} which use an {@link InputStream} for reading bytes,
* without having to read the entire stream in memory.
* <p/>
* NOTE: The {@link InputStream} must support the {@link InputStream#mark} and
* {@link java.io.InputStream#reset()} operation. If not, mechanisms like
* authentication, redirect, or resumable download will not works.
*/
public class InputStreamBodyGenerator implements BodyGenerator {
private final static byte[] END_PADDING = "\r\n".getBytes();
private final static byte[] ZERO = "0".getBytes();
private final InputStream inputStream;
private boolean patchNettyChunkingIssue = false;
public InputStreamBodyGenerator(final InputStream inputStream) {
this.inputStream = inputStream;
if (inputStream.markSupported()) {
inputStream.mark(0);
} else {
}
}
/**
* {@inheritDoc}
*/
/* @Override */
@Override
public Body createBody() throws IOException {
return new ISBody();
}
protected class ISBody implements Body {
private boolean eof = false;
private int endDataCount = 0;
private byte[] chunk;
@Override
public long getContentLength() {
return -1;
}
@Override
public long read(final ByteBuffer buffer) throws IOException {
// To be safe.
chunk = new byte[buffer.remaining() - 10];
int read = -1;
try {
read = inputStream.read(chunk);
} catch (final IOException ex) {
}
if (patchNettyChunkingIssue) {
if (read == -1) {
// Since we are chuncked, we must output extra bytes before
// considering the input stream closed.
// chunking requires to end the chunking:
// - A Terminating chunk of "0\r\n".getBytes(),
// - Then a separate packet of "\r\n".getBytes()
if (!eof) {
endDataCount++;
if (endDataCount == 2)
eof = true;
if (endDataCount == 1)
buffer.put(ZERO);
buffer.put(END_PADDING);
return buffer.position();
} else {
if (inputStream.markSupported()) {
inputStream.reset();
}
eof = false;
}
return -1;
}
/**
* Netty 3.2.3 doesn't support chunking encoding properly, so we
* chunk encoding ourself.
*/
buffer.put(Integer.toHexString(read).getBytes());
// Chunking is separated by "<bytesreads>\r\n"
buffer.put(END_PADDING);
buffer.put(chunk, 0, read);
// Was missing the final chunk \r\n.
buffer.put(END_PADDING);
} else {
if (read > 0) {
buffer.put(chunk, 0, read);
}
}
return read;
}
@Override
public void close() throws IOException {
inputStream.close();
}
}
/**
* HACK: This is required because Netty has issues with chunking.
*
* @param patchNettyChunkingIssue
*/
public void patchNettyChunkingIssue(final boolean patchNettyChunkingIssue) {
this.patchNettyChunkingIssue = patchNettyChunkingIssue;
}
}