1
0
mirror of https://github.com/arduino-libraries/ArduinoHttpClient.git synced 2025-04-19 21:22:15 +03:00

Add support for chunked response bodies

This commit is contained in:
Sandeep Mistry 2017-03-23 14:41:41 -04:00
parent 5bda5b6f07
commit 522cf5d11a
3 changed files with 120 additions and 7 deletions

View File

@ -30,6 +30,7 @@ endOfHeadersReached KEYWORD2
endOfBodyReached KEYWORD2 endOfBodyReached KEYWORD2
completed KEYWORD2 completed KEYWORD2
contentLength KEYWORD2 contentLength KEYWORD2
isChunked KEYWORD2
connectionKeepAlive KEYWORD2 connectionKeepAlive KEYWORD2
noDefaultRequestHeaders KEYWORD2 noDefaultRequestHeaders KEYWORD2
headerAvailable KEYWORD2 headerAvailable KEYWORD2

View File

@ -8,6 +8,7 @@
// Initialize constants // Initialize constants
const char* HttpClient::kUserAgent = "Arduino/2.2.0"; const char* HttpClient::kUserAgent = "Arduino/2.2.0";
const char* HttpClient::kContentLengthPrefix = HTTP_HEADER_CONTENT_LENGTH ": "; const char* HttpClient::kContentLengthPrefix = HTTP_HEADER_CONTENT_LENGTH ": ";
const char* HttpClient::kTransferEncodingChunked = HTTP_HEADER_TRANSFER_ENCODING ": " HTTP_HEADER_VALUE_CHUNKED;
HttpClient::HttpClient(Client& aClient, const char* aServerName, uint16_t aServerPort) HttpClient::HttpClient(Client& aClient, const char* aServerName, uint16_t aServerPort)
: iClient(&aClient), iServerName(aServerName), iServerAddress(), iServerPort(aServerPort), : iClient(&aClient), iServerName(aServerName), iServerAddress(), iServerPort(aServerPort),
@ -35,6 +36,9 @@ void HttpClient::resetState()
iContentLength = kNoContentLengthHeader; iContentLength = kNoContentLengthHeader;
iBodyLengthConsumed = 0; iBodyLengthConsumed = 0;
iContentLengthPtr = kContentLengthPrefix; iContentLengthPtr = kContentLengthPrefix;
iTransferEncodingChunkedPtr = kTransferEncodingChunked;
iIsChunked = false;
iChunkLength = 0;
iHttpResponseTimeout = kHttpResponseTimeout; iHttpResponseTimeout = kHttpResponseTimeout;
} }
@ -62,7 +66,7 @@ void HttpClient::beginRequest()
int HttpClient::startRequest(const char* aURLPath, const char* aHttpMethod, int HttpClient::startRequest(const char* aURLPath, const char* aHttpMethod,
const char* aContentType, int aContentLength, const byte aBody[]) const char* aContentType, int aContentLength, const byte aBody[])
{ {
if (iState == eReadingBody) if (iState == eReadingBody || iState == eReadingChunkLength || iState == eReadingBodyChunk)
{ {
flushClientRx(); flushClientRx();
@ -528,6 +532,11 @@ int HttpClient::skipResponseHeaders()
} }
} }
bool HttpClient::endOfHeadersReached()
{
return (iState == eReadingBody || iState == eReadingChunkLength || iState == eReadingBodyChunk);
};
int HttpClient::contentLength() int HttpClient::contentLength()
{ {
// skip the response headers, if they haven't been read already // skip the response headers, if they haven't been read already
@ -590,8 +599,65 @@ bool HttpClient::endOfBodyReached()
return false; return false;
} }
int HttpClient::available()
{
if (iState == eReadingChunkLength)
{
while (iClient->available())
{
char c = iClient->read();
if (c == '\n')
{
iState = eReadingBodyChunk;
break;
}
else if (c == '\r')
{
// no-op
}
else if (isHexadecimalDigit(c))
{
char digit[2] = {c, '\0'};
iChunkLength = (iChunkLength * 16) + strtol(digit, NULL, 16);
}
}
}
if (iState == eReadingBodyChunk && iChunkLength == 0)
{
iState = eReadingChunkLength;
}
if (iState == eReadingChunkLength)
{
return 0;
}
int clientAvailable = iClient->available();
if (iState == eReadingBodyChunk)
{
return min(clientAvailable, iChunkLength);
}
else
{
return clientAvailable;
}
}
int HttpClient::read() int HttpClient::read()
{ {
if (iState == eReadingBodyChunk)
{
if (!available())
{
return -1;
}
}
int ret = iClient->read(); int ret = iClient->read();
if (ret >= 0) if (ret >= 0)
{ {
@ -601,6 +667,16 @@ int HttpClient::read()
// So keep track of how many bytes are left // So keep track of how many bytes are left
iBodyLengthConsumed++; iBodyLengthConsumed++;
} }
if (iState == eReadingBodyChunk)
{
iChunkLength--;
if (iChunkLength == 0)
{
iState = eReadingChunkLength;
}
}
} }
return ret; return ret;
} }
@ -714,7 +790,18 @@ int HttpClient::readHeader()
iBodyLengthConsumed = 0; iBodyLengthConsumed = 0;
} }
} }
else if ((iContentLengthPtr == kContentLengthPrefix) && (c == '\r')) else if (*iTransferEncodingChunkedPtr == c)
{
// This character matches, just move along
iTransferEncodingChunkedPtr++;
if (*iTransferEncodingChunkedPtr == '\0')
{
// We've reached the end of the Transfer Encoding: chunked header
iIsChunked = true;
iState = eSkipToEndOfHeader;
}
}
else if (((iContentLengthPtr == kContentLengthPrefix) && (iTransferEncodingChunkedPtr == kTransferEncodingChunked)) && (c == '\r'))
{ {
// We've found a '\r' at the start of a line, so this is probably // We've found a '\r' at the start of a line, so this is probably
// the end of the headers // the end of the headers
@ -722,7 +809,7 @@ int HttpClient::readHeader()
} }
else else
{ {
// This isn't the Content-Length header, skip to the end of the line // This isn't the Content-Length or Transfer Encoding chunked header, skip to the end of the line
iState = eSkipToEndOfHeader; iState = eSkipToEndOfHeader;
} }
break; break;
@ -741,9 +828,17 @@ int HttpClient::readHeader()
break; break;
case eLineStartingCRFound: case eLineStartingCRFound:
if (c == '\n') if (c == '\n')
{
if (iIsChunked)
{
iState = eReadingChunkLength;
iChunkLength = 0;
}
else
{ {
iState = eReadingBody; iState = eReadingBody;
} }
}
break; break;
default: default:
// We're just waiting for the end of the line now // We're just waiting for the end of the line now
@ -755,6 +850,7 @@ int HttpClient::readHeader()
// We've got to the end of this line, start processing again // We've got to the end of this line, start processing again
iState = eStatusCodeRead; iState = eStatusCodeRead;
iContentLengthPtr = kContentLengthPrefix; iContentLengthPtr = kContentLengthPrefix;
iTransferEncodingChunkedPtr = kTransferEncodingChunked;
} }
// And return the character read to whoever wants it // And return the character read to whoever wants it
return c; return c;

View File

@ -34,7 +34,9 @@ static const int HTTP_ERROR_INVALID_RESPONSE =-4;
#define HTTP_HEADER_CONTENT_LENGTH "Content-Length" #define HTTP_HEADER_CONTENT_LENGTH "Content-Length"
#define HTTP_HEADER_CONTENT_TYPE "Content-Type" #define HTTP_HEADER_CONTENT_TYPE "Content-Type"
#define HTTP_HEADER_CONNECTION "Connection" #define HTTP_HEADER_CONNECTION "Connection"
#define HTTP_HEADER_TRANSFER_ENCODING "Transfer-Encoding"
#define HTTP_HEADER_USER_AGENT "User-Agent" #define HTTP_HEADER_USER_AGENT "User-Agent"
#define HTTP_HEADER_VALUE_CHUNKED "chunked"
class HttpClient : public Client class HttpClient : public Client
{ {
@ -247,7 +249,7 @@ public:
/** Test whether all of the response headers have been consumed. /** Test whether all of the response headers have been consumed.
@return true if we are now processing the response body, else false @return true if we are now processing the response body, else false
*/ */
bool endOfHeadersReached() { return (iState == eReadingBody); }; bool endOfHeadersReached();
/** Test whether the end of the body has been reached. /** Test whether the end of the body has been reached.
Only works if the Content-Length header was returned by the server Only works if the Content-Length header was returned by the server
@ -265,6 +267,11 @@ public:
*/ */
int contentLength(); int contentLength();
/** Returns if the response body is chunked
@return true if response body is chunked, false otherwise
*/
int isChunked() { return iIsChunked; }
/** Return the response body as a String /** Return the response body as a String
Also skips response headers if they have not been read already Also skips response headers if they have not been read already
MUST be called after responseStatusCode() MUST be called after responseStatusCode()
@ -286,7 +293,7 @@ public:
virtual size_t write(uint8_t aByte) { if (iState < eRequestSent) { finishHeaders(); }; return iClient-> write(aByte); }; virtual size_t write(uint8_t aByte) { if (iState < eRequestSent) { finishHeaders(); }; return iClient-> write(aByte); };
virtual size_t write(const uint8_t *aBuffer, size_t aSize) { if (iState < eRequestSent) { finishHeaders(); }; return iClient->write(aBuffer, aSize); }; virtual size_t write(const uint8_t *aBuffer, size_t aSize) { if (iState < eRequestSent) { finishHeaders(); }; return iClient->write(aBuffer, aSize); };
// Inherited from Stream // Inherited from Stream
virtual int available() { return iClient->available(); }; virtual int available();
/** Read the next byte from the server. /** Read the next byte from the server.
@return Byte read or -1 if there are no bytes available. @return Byte read or -1 if there are no bytes available.
*/ */
@ -332,6 +339,7 @@ protected:
// processing) // processing)
static const int kHttpResponseTimeout = 30*1000; static const int kHttpResponseTimeout = 30*1000;
static const char* kContentLengthPrefix; static const char* kContentLengthPrefix;
static const char* kTransferEncodingChunked;
typedef enum { typedef enum {
eIdle, eIdle,
eRequestStarted, eRequestStarted,
@ -341,7 +349,9 @@ protected:
eReadingContentLength, eReadingContentLength,
eSkipToEndOfHeader, eSkipToEndOfHeader,
eLineStartingCRFound, eLineStartingCRFound,
eReadingBody eReadingBody,
eReadingChunkLength,
eReadingBodyChunk
} tHttpState; } tHttpState;
// Client we're using // Client we're using
Client* iClient; Client* iClient;
@ -360,6 +370,12 @@ protected:
int iBodyLengthConsumed; int iBodyLengthConsumed;
// How far through a Content-Length header prefix we are // How far through a Content-Length header prefix we are
const char* iContentLengthPtr; const char* iContentLengthPtr;
// How far through a Transfer-Encoding chunked header we are
const char* iTransferEncodingChunkedPtr;
// Stores if the response body is chunked
bool iIsChunked;
// Stores the value of the current chunk length, if present
int iChunkLength;
uint32_t iHttpResponseTimeout; uint32_t iHttpResponseTimeout;
bool iConnectionClose; bool iConnectionClose;
bool iSendDefaultRequestHeaders; bool iSendDefaultRequestHeaders;