Merge "Support for "chunked" HTTP transfer encoding."

This commit is contained in:
Andreas Huber
2010-11-18 13:19:07 -08:00
committed by Android (Google) Code Review
4 changed files with 134 additions and 19 deletions

View File

@@ -69,6 +69,8 @@ NuHTTPDataSource::NuHTTPDataSource()
mOffset(0),
mContentLength(0),
mContentLengthValid(false),
mHasChunkedTransferEncoding(false),
mChunkDataBytesLeft(0),
mNumBandwidthHistoryItems(0),
mTotalTransferTimeUs(0),
mTotalTransferBytes(0),
@@ -193,17 +195,27 @@ status_t NuHTTPDataSource::connect(
return ERROR_IO;
}
mHasChunkedTransferEncoding = false;
{
string value;
if (mHTTP.find_header_value("Transfer-Encoding", &value)) {
// We don't currently support any transfer encodings.
if (mHTTP.find_header_value("Transfer-Encoding", &value)
|| mHTTP.find_header_value("Transfer-encoding", &value)) {
// We don't currently support any transfer encodings but
// chunked.
mState = DISCONNECTED;
mHTTP.disconnect();
if (!strcasecmp(value.c_str(), "chunked")) {
LOGI("Chunked transfer encoding applied.");
mHasChunkedTransferEncoding = true;
mChunkDataBytesLeft = 0;
} else {
mState = DISCONNECTED;
mHTTP.disconnect();
LOGE("We don't support '%s' transfer encoding.", value.c_str());
LOGE("We don't support '%s' transfer encoding.", value.c_str());
return ERROR_UNSUPPORTED;
return ERROR_UNSUPPORTED;
}
}
}
@@ -216,8 +228,17 @@ status_t NuHTTPDataSource::connect(
&& ParseSingleUnsignedLong(value.c_str(), &x)) {
mContentLength = (off_t)x;
mContentLengthValid = true;
} else {
LOGW("Server did not give us the content length!");
}
} else {
if (httpStatus != 206 /* Partial Content */) {
// We requested a range but the server didn't support that.
LOGE("We requested a range but the server didn't "
"support that.");
return ERROR_UNSUPPORTED;
}
string value;
unsigned long x;
if (mHTTP.find_header_value(string("Content-Range"), &value)) {
@@ -245,6 +266,71 @@ status_t NuHTTPDataSource::initCheck() const {
return mState == CONNECTED ? OK : NO_INIT;
}
ssize_t NuHTTPDataSource::internalRead(void *data, size_t size) {
if (!mHasChunkedTransferEncoding) {
return mHTTP.receive(data, size);
}
if (mChunkDataBytesLeft < 0) {
return 0;
} else if (mChunkDataBytesLeft == 0) {
char line[1024];
status_t err = mHTTP.receive_line(line, sizeof(line));
if (err != OK) {
return err;
}
LOGV("line = '%s'", line);
char *end;
unsigned long n = strtoul(line, &end, 16);
if (end == line || (*end != ';' && *end != '\0')) {
LOGE("malformed HTTP chunk '%s'", line);
return ERROR_MALFORMED;
}
mChunkDataBytesLeft = n;
LOGV("chunk data size = %lu", n);
if (mChunkDataBytesLeft == 0) {
mChunkDataBytesLeft = -1;
return 0;
}
// fall through
}
if (size > (size_t)mChunkDataBytesLeft) {
size = mChunkDataBytesLeft;
}
ssize_t n = mHTTP.receive(data, size);
if (n < 0) {
return n;
}
mChunkDataBytesLeft -= (size_t)n;
if (mChunkDataBytesLeft == 0) {
char line[1024];
status_t err = mHTTP.receive_line(line, sizeof(line));
if (err != OK) {
return err;
}
if (line[0] != '\0') {
LOGE("missing HTTP chunk terminator.");
return ERROR_MALFORMED;
}
}
return n;
}
ssize_t NuHTTPDataSource::readAt(off_t offset, void *data, size_t size) {
LOGV("readAt offset %ld, size %d", offset, size);
@@ -275,7 +361,7 @@ ssize_t NuHTTPDataSource::readAt(off_t offset, void *data, size_t size) {
int64_t startTimeUs = ALooper::GetNowUs();
ssize_t n =
mHTTP.receive((uint8_t *)data + numBytesRead, size - numBytesRead);
internalRead((uint8_t *)data + numBytesRead, size - numBytesRead);
if (n < 0) {
return n;

View File

@@ -555,20 +555,41 @@ status_t LiveSource::fetchM3U(const char *url, sp<ABuffer> *out) {
status_t err = source->getSize(&size);
if (err != OK) {
return err;
size = 65536;
}
sp<ABuffer> buffer = new ABuffer(size);
size_t offset = 0;
while (offset < (size_t)size) {
ssize_t n = source->readAt(
offset, buffer->data() + offset, size - offset);
buffer->setRange(0, 0);
if (n <= 0) {
return ERROR_IO;
for (;;) {
size_t bufferRemaining = buffer->capacity() - buffer->size();
if (bufferRemaining == 0) {
bufferRemaining = 32768;
LOGV("increasing download buffer to %d bytes",
buffer->size() + bufferRemaining);
sp<ABuffer> copy = new ABuffer(buffer->size() + bufferRemaining);
memcpy(copy->data(), buffer->data(), buffer->size());
copy->setRange(0, buffer->size());
buffer = copy;
}
offset += n;
ssize_t n = source->readAt(
buffer->size(), buffer->data() + buffer->size(),
bufferRemaining);
if (n < 0) {
return err;
}
if (n == 0) {
break;
}
buffer->setRange(0, buffer->size() + (size_t)n);
}
*out = buffer;

View File

@@ -55,6 +55,10 @@ public:
// Pass a negative value to disable the timeout.
void setReceiveTimeout(int seconds);
// Receive a line of data terminated by CRLF, line will be '\0' terminated
// _excluding_ the termianting CRLF.
status_t receive_line(char *line, size_t size);
private:
enum State {
READY,
@@ -68,10 +72,6 @@ private:
KeyedVector<string, string> mHeaders;
// Receive a line of data terminated by CRLF, line will be '\0' terminated
// _excluding_ the termianting CRLF.
status_t receive_line(char *line, size_t size);
HTTPStream(const HTTPStream &);
HTTPStream &operator=(const HTTPStream &);
};

View File

@@ -64,6 +64,11 @@ private:
off_t mOffset;
off_t mContentLength;
bool mContentLengthValid;
bool mHasChunkedTransferEncoding;
// The number of data bytes in the current chunk before any subsequent
// chunk header (or -1 if no more chunks).
ssize_t mChunkDataBytesLeft;
List<BandwidthEntry> mBandwidthHistory;
size_t mNumBandwidthHistoryItems;
@@ -81,6 +86,9 @@ private:
const String8 &headers,
off_t offset);
// Read up to "size" bytes of data, respect transfer encoding.
ssize_t internalRead(void *data, size_t size);
void applyTimeoutResponse();
void addBandwidthMeasurement_l(size_t numBytes, int64_t delayUs);