Skip to content

Commit 522cf5d

Browse files
committed
Add support for chunked response bodies
1 parent 5bda5b6 commit 522cf5d

File tree

3 files changed

+120
-7
lines changed

3 files changed

+120
-7
lines changed

keywords.txt

+1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ endOfHeadersReached KEYWORD2
3030
endOfBodyReached KEYWORD2
3131
completed KEYWORD2
3232
contentLength KEYWORD2
33+
isChunked KEYWORD2
3334
connectionKeepAlive KEYWORD2
3435
noDefaultRequestHeaders KEYWORD2
3536
headerAvailable KEYWORD2

src/HttpClient.cpp

+100-4
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
// Initialize constants
99
const char* HttpClient::kUserAgent = "Arduino/2.2.0";
1010
const char* HttpClient::kContentLengthPrefix = HTTP_HEADER_CONTENT_LENGTH ": ";
11+
const char* HttpClient::kTransferEncodingChunked = HTTP_HEADER_TRANSFER_ENCODING ": " HTTP_HEADER_VALUE_CHUNKED;
1112

1213
HttpClient::HttpClient(Client& aClient, const char* aServerName, uint16_t aServerPort)
1314
: iClient(&aClient), iServerName(aServerName), iServerAddress(), iServerPort(aServerPort),
@@ -35,6 +36,9 @@ void HttpClient::resetState()
3536
iContentLength = kNoContentLengthHeader;
3637
iBodyLengthConsumed = 0;
3738
iContentLengthPtr = kContentLengthPrefix;
39+
iTransferEncodingChunkedPtr = kTransferEncodingChunked;
40+
iIsChunked = false;
41+
iChunkLength = 0;
3842
iHttpResponseTimeout = kHttpResponseTimeout;
3943
}
4044

@@ -62,7 +66,7 @@ void HttpClient::beginRequest()
6266
int HttpClient::startRequest(const char* aURLPath, const char* aHttpMethod,
6367
const char* aContentType, int aContentLength, const byte aBody[])
6468
{
65-
if (iState == eReadingBody)
69+
if (iState == eReadingBody || iState == eReadingChunkLength || iState == eReadingBodyChunk)
6670
{
6771
flushClientRx();
6872

@@ -528,6 +532,11 @@ int HttpClient::skipResponseHeaders()
528532
}
529533
}
530534

535+
bool HttpClient::endOfHeadersReached()
536+
{
537+
return (iState == eReadingBody || iState == eReadingChunkLength || iState == eReadingBodyChunk);
538+
};
539+
531540
int HttpClient::contentLength()
532541
{
533542
// skip the response headers, if they haven't been read already
@@ -590,8 +599,65 @@ bool HttpClient::endOfBodyReached()
590599
return false;
591600
}
592601

602+
int HttpClient::available()
603+
{
604+
if (iState == eReadingChunkLength)
605+
{
606+
while (iClient->available())
607+
{
608+
char c = iClient->read();
609+
610+
if (c == '\n')
611+
{
612+
iState = eReadingBodyChunk;
613+
break;
614+
}
615+
else if (c == '\r')
616+
{
617+
// no-op
618+
}
619+
else if (isHexadecimalDigit(c))
620+
{
621+
char digit[2] = {c, '\0'};
622+
623+
iChunkLength = (iChunkLength * 16) + strtol(digit, NULL, 16);
624+
}
625+
}
626+
}
627+
628+
if (iState == eReadingBodyChunk && iChunkLength == 0)
629+
{
630+
iState = eReadingChunkLength;
631+
}
632+
633+
if (iState == eReadingChunkLength)
634+
{
635+
return 0;
636+
}
637+
638+
int clientAvailable = iClient->available();
639+
640+
if (iState == eReadingBodyChunk)
641+
{
642+
return min(clientAvailable, iChunkLength);
643+
}
644+
else
645+
{
646+
return clientAvailable;
647+
}
648+
}
649+
650+
593651
int HttpClient::read()
594652
{
653+
if (iState == eReadingBodyChunk)
654+
{
655+
if (!available())
656+
{
657+
return -1;
658+
}
659+
}
660+
595661
int ret = iClient->read();
596662
if (ret >= 0)
597663
{
@@ -601,6 +667,16 @@ int HttpClient::read()
601667
// So keep track of how many bytes are left
602668
iBodyLengthConsumed++;
603669
}
670+
671+
if (iState == eReadingBodyChunk)
672+
{
673+
iChunkLength--;
674+
675+
if (iChunkLength == 0)
676+
{
677+
iState = eReadingChunkLength;
678+
}
679+
}
604680
}
605681
return ret;
606682
}
@@ -714,15 +790,26 @@ int HttpClient::readHeader()
714790
iBodyLengthConsumed = 0;
715791
}
716792
}
717-
else if ((iContentLengthPtr == kContentLengthPrefix) && (c == '\r'))
793+
else if (*iTransferEncodingChunkedPtr == c)
794+
{
795+
// This character matches, just move along
796+
iTransferEncodingChunkedPtr++;
797+
if (*iTransferEncodingChunkedPtr == '\0')
798+
{
799+
// We've reached the end of the Transfer Encoding: chunked header
800+
iIsChunked = true;
801+
iState = eSkipToEndOfHeader;
802+
}
803+
}
804+
else if (((iContentLengthPtr == kContentLengthPrefix) && (iTransferEncodingChunkedPtr == kTransferEncodingChunked)) && (c == '\r'))
718805
{
719806
// We've found a '\r' at the start of a line, so this is probably
720807
// the end of the headers
721808
iState = eLineStartingCRFound;
722809
}
723810
else
724811
{
725-
// This isn't the Content-Length header, skip to the end of the line
812+
// This isn't the Content-Length or Transfer Encoding chunked header, skip to the end of the line
726813
iState = eSkipToEndOfHeader;
727814
}
728815
break;
@@ -742,7 +829,15 @@ int HttpClient::readHeader()
742829
case eLineStartingCRFound:
743830
if (c == '\n')
744831
{
745-
iState = eReadingBody;
832+
if (iIsChunked)
833+
{
834+
iState = eReadingChunkLength;
835+
iChunkLength = 0;
836+
}
837+
else
838+
{
839+
iState = eReadingBody;
840+
}
746841
}
747842
break;
748843
default:
@@ -755,6 +850,7 @@ int HttpClient::readHeader()
755850
// We've got to the end of this line, start processing again
756851
iState = eStatusCodeRead;
757852
iContentLengthPtr = kContentLengthPrefix;
853+
iTransferEncodingChunkedPtr = kTransferEncodingChunked;
758854
}
759855
// And return the character read to whoever wants it
760856
return c;

src/HttpClient.h

+19-3
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,9 @@ static const int HTTP_ERROR_INVALID_RESPONSE =-4;
3434
#define HTTP_HEADER_CONTENT_LENGTH "Content-Length"
3535
#define HTTP_HEADER_CONTENT_TYPE "Content-Type"
3636
#define HTTP_HEADER_CONNECTION "Connection"
37+
#define HTTP_HEADER_TRANSFER_ENCODING "Transfer-Encoding"
3738
#define HTTP_HEADER_USER_AGENT "User-Agent"
39+
#define HTTP_HEADER_VALUE_CHUNKED "chunked"
3840

3941
class HttpClient : public Client
4042
{
@@ -247,7 +249,7 @@ class HttpClient : public Client
247249
/** Test whether all of the response headers have been consumed.
248250
@return true if we are now processing the response body, else false
249251
*/
250-
bool endOfHeadersReached() { return (iState == eReadingBody); };
252+
bool endOfHeadersReached();
251253

252254
/** Test whether the end of the body has been reached.
253255
Only works if the Content-Length header was returned by the server
@@ -265,6 +267,11 @@ class HttpClient : public Client
265267
*/
266268
int contentLength();
267269

270+
/** Returns if the response body is chunked
271+
@return true if response body is chunked, false otherwise
272+
*/
273+
int isChunked() { return iIsChunked; }
274+
268275
/** Return the response body as a String
269276
Also skips response headers if they have not been read already
270277
MUST be called after responseStatusCode()
@@ -286,7 +293,7 @@ class HttpClient : public Client
286293
virtual size_t write(uint8_t aByte) { if (iState < eRequestSent) { finishHeaders(); }; return iClient-> write(aByte); };
287294
virtual size_t write(const uint8_t *aBuffer, size_t aSize) { if (iState < eRequestSent) { finishHeaders(); }; return iClient->write(aBuffer, aSize); };
288295
// Inherited from Stream
289-
virtual int available() { return iClient->available(); };
296+
virtual int available();
290297
/** Read the next byte from the server.
291298
@return Byte read or -1 if there are no bytes available.
292299
*/
@@ -332,6 +339,7 @@ class HttpClient : public Client
332339
// processing)
333340
static const int kHttpResponseTimeout = 30*1000;
334341
static const char* kContentLengthPrefix;
342+
static const char* kTransferEncodingChunked;
335343
typedef enum {
336344
eIdle,
337345
eRequestStarted,
@@ -341,7 +349,9 @@ class HttpClient : public Client
341349
eReadingContentLength,
342350
eSkipToEndOfHeader,
343351
eLineStartingCRFound,
344-
eReadingBody
352+
eReadingBody,
353+
eReadingChunkLength,
354+
eReadingBodyChunk
345355
} tHttpState;
346356
// Client we're using
347357
Client* iClient;
@@ -360,6 +370,12 @@ class HttpClient : public Client
360370
int iBodyLengthConsumed;
361371
// How far through a Content-Length header prefix we are
362372
const char* iContentLengthPtr;
373+
// How far through a Transfer-Encoding chunked header we are
374+
const char* iTransferEncodingChunkedPtr;
375+
// Stores if the response body is chunked
376+
bool iIsChunked;
377+
// Stores the value of the current chunk length, if present
378+
int iChunkLength;
363379
uint32_t iHttpResponseTimeout;
364380
bool iConnectionClose;
365381
bool iSendDefaultRequestHeaders;

0 commit comments

Comments
 (0)