Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 765f2db

Browse filesBrowse files
authored
Merge pull request arduino-libraries#21 from sandeepmistry/chunked-response-body-support
Add support for chunked response bodies
2 parents 448a152 + 0dd9f42 commit 765f2db
Copy full SHA for 765f2db

File tree

4 files changed

+162
-7
lines changed
Filter options

4 files changed

+162
-7
lines changed

‎examples/node_test_server/getPostPutDelete.js

Copy file name to clipboardExpand all lines: examples/node_test_server/getPostPutDelete.js
+45Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,51 @@ function serverStart() {
2121
console.log('Server listening on port '+ port);
2222
}
2323

24+
app.get('/chunked', function(request, response) {
25+
response.write('\n');
26+
response.write(' `:;;;,` .:;;:. \n');
27+
response.write(' .;;;;;;;;;;;` :;;;;;;;;;;: TM \n');
28+
response.write(' `;;;;;;;;;;;;;;;` :;;;;;;;;;;;;;;; \n');
29+
response.write(' :;;;;;;;;;;;;;;;;;; `;;;;;;;;;;;;;;;;;; \n');
30+
response.write(' ;;;;;;;;;;;;;;;;;;;;; .;;;;;;;;;;;;;;;;;;;; \n');
31+
response.write(' ;;;;;;;;:` `;;;;;;;;; ,;;;;;;;;.` .;;;;;;;; \n');
32+
response.write(' .;;;;;;, :;;;;;;; .;;;;;;; ;;;;;;; \n');
33+
response.write(' ;;;;;; ;;;;;;; ;;;;;;, ;;;;;;. \n');
34+
response.write(' ,;;;;; ;;;;;;.;;;;;;` ;;;;;; \n');
35+
response.write(' ;;;;;. ;;;;;;;;;;;` ``` ;;;;;`\n');
36+
response.write(' ;;;;; ;;;;;;;;;, ;;; .;;;;;\n');
37+
response.write('`;;;;: `;;;;;;;; ;;; ;;;;;\n');
38+
response.write(',;;;;` `,,,,,,,, ;;;;;;; .,,;;;,,, ;;;;;\n');
39+
response.write(':;;;;` .;;;;;;;; ;;;;;, :;;;;;;;; ;;;;;\n');
40+
response.write(':;;;;` .;;;;;;;; `;;;;;; :;;;;;;;; ;;;;;\n');
41+
response.write('.;;;;. ;;;;;;;. ;;; ;;;;;\n');
42+
response.write(' ;;;;; ;;;;;;;;; ;;; ;;;;;\n');
43+
response.write(' ;;;;; .;;;;;;;;;; ;;; ;;;;;,\n');
44+
response.write(' ;;;;;; `;;;;;;;;;;;; ;;;;; \n');
45+
response.write(' `;;;;;, .;;;;;; ;;;;;;; ;;;;;; \n');
46+
response.write(' ;;;;;;: :;;;;;;. ;;;;;;; ;;;;;; \n');
47+
response.write(' ;;;;;;;` .;;;;;;;, ;;;;;;;; ;;;;;;;: \n');
48+
response.write(' ;;;;;;;;;:,:;;;;;;;;;: ;;;;;;;;;;:,;;;;;;;;;; \n');
49+
response.write(' `;;;;;;;;;;;;;;;;;;;. ;;;;;;;;;;;;;;;;;;;; \n');
50+
response.write(' ;;;;;;;;;;;;;;;;; :;;;;;;;;;;;;;;;;: \n');
51+
response.write(' ,;;;;;;;;;;;;;, ;;;;;;;;;;;;;; \n');
52+
response.write(' .;;;;;;;;;` ,;;;;;;;;: \n');
53+
response.write(' \n');
54+
response.write(' \n');
55+
response.write(' \n');
56+
response.write(' \n');
57+
response.write(' ;;; ;;;;;` ;;;;: .;; ;; ,;;;;;, ;;. `;, ;;;; \n');
58+
response.write(' ;;; ;;:;;; ;;;;;; .;; ;; ,;;;;;: ;;; `;, ;;;:;; \n');
59+
response.write(' ,;:; ;; ;; ;; ;; .;; ;; ,;, ;;;,`;, ;; ;; \n');
60+
response.write(' ;; ;: ;; ;; ;; ;; .;; ;; ,;, ;;;;`;, ;; ;;. \n');
61+
response.write(' ;: ;; ;;;;;: ;; ;; .;; ;; ,;, ;;`;;;, ;; ;;` \n');
62+
response.write(' ,;;;;; ;;`;; ;; ;; .;; ;; ,;, ;; ;;;, ;; ;; \n');
63+
response.write(' ;; ,;, ;; .;; ;;;;;: ;;;;;: ,;;;;;: ;; ;;, ;;;;;; \n');
64+
response.write(' ;; ;; ;; ;;` ;;;;. `;;;: ,;;;;;, ;; ;;, ;;;; \n');
65+
response.write('\n');
66+
response.end();
67+
});
68+
2469
// this is the POST handler:
2570
app.all('/*', function (request, response) {
2671
console.log('Got a ' + request.method + ' request');

‎keywords.txt

Copy file name to clipboardExpand all lines: keywords.txt
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ endOfHeadersReached KEYWORD2
3131
endOfBodyReached KEYWORD2
3232
completed KEYWORD2
3333
contentLength KEYWORD2
34+
isResponseChunked KEYWORD2
3435
connectionKeepAlive KEYWORD2
3536
noDefaultRequestHeaders KEYWORD2
3637
headerAvailable KEYWORD2

‎src/HttpClient.cpp

Copy file name to clipboardExpand all lines: src/HttpClient.cpp
+97-4Lines changed: 97 additions & 4 deletions
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

@@ -533,6 +537,11 @@ int HttpClient::skipResponseHeaders()
533537
}
534538
}
535539

540+
bool HttpClient::endOfHeadersReached()
541+
{
542+
return (iState == eReadingBody || iState == eReadingChunkLength || iState == eReadingBodyChunk);
543+
};
544+
536545
int HttpClient::contentLength()
537546
{
538547
// skip the response headers, if they haven't been read already
@@ -595,8 +604,62 @@ bool HttpClient::endOfBodyReached()
595604
return false;
596605
}
597606

607+
int HttpClient::available()
608+
{
609+
if (iState == eReadingChunkLength)
610+
{
611+
while (iClient->available())
612+
{
613+
char c = iClient->read();
614+
615+
if (c == '\n')
616+
{
617+
iState = eReadingBodyChunk;
618+
break;
619+
}
620+
else if (c == '\r')
621+
{
622+
// no-op
623+
}
624+
else if (isHexadecimalDigit(c))
625+
{
626+
char digit[2] = {c, '\0'};
627+
628+
iChunkLength = (iChunkLength * 16) + strtol(digit, NULL, 16);
629+
}
630+
}
631+
}
632+
633+
if (iState == eReadingBodyChunk && iChunkLength == 0)
634+
{
635+
iState = eReadingChunkLength;
636+
}
637+
638+
if (iState == eReadingChunkLength)
639+
{
640+
return 0;
641+
}
642+
643+
int clientAvailable = iClient->available();
644+
645+
if (iState == eReadingBodyChunk)
646+
{
647+
return min(clientAvailable, iChunkLength);
648+
}
649+
else
650+
{
651+
return clientAvailable;
652+
}
653+
}
654+
655+
598656
int HttpClient::read()
599657
{
658+
if (iIsChunked && !available())
659+
{
660+
return -1;
661+
}
662+
600663
int ret = iClient->read();
601664
if (ret >= 0)
602665
{
@@ -606,6 +669,16 @@ int HttpClient::read()
606669
// So keep track of how many bytes are left
607670
iBodyLengthConsumed++;
608671
}
672+
673+
if (iState == eReadingBodyChunk)
674+
{
675+
iChunkLength--;
676+
677+
if (iChunkLength == 0)
678+
{
679+
iState = eReadingChunkLength;
680+
}
681+
}
609682
}
610683
return ret;
611684
}
@@ -719,15 +792,26 @@ int HttpClient::readHeader()
719792
iBodyLengthConsumed = 0;
720793
}
721794
}
722-
else if ((iContentLengthPtr == kContentLengthPrefix) && (c == '\r'))
795+
else if (*iTransferEncodingChunkedPtr == c)
796+
{
797+
// This character matches, just move along
798+
iTransferEncodingChunkedPtr++;
799+
if (*iTransferEncodingChunkedPtr == '\0')
800+
{
801+
// We've reached the end of the Transfer Encoding: chunked header
802+
iIsChunked = true;
803+
iState = eSkipToEndOfHeader;
804+
}
805+
}
806+
else if (((iContentLengthPtr == kContentLengthPrefix) && (iTransferEncodingChunkedPtr == kTransferEncodingChunked)) && (c == '\r'))
723807
{
724808
// We've found a '\r' at the start of a line, so this is probably
725809
// the end of the headers
726810
iState = eLineStartingCRFound;
727811
}
728812
else
729813
{
730-
// This isn't the Content-Length header, skip to the end of the line
814+
// This isn't the Content-Length or Transfer Encoding chunked header, skip to the end of the line
731815
iState = eSkipToEndOfHeader;
732816
}
733817
break;
@@ -747,7 +831,15 @@ int HttpClient::readHeader()
747831
case eLineStartingCRFound:
748832
if (c == '\n')
749833
{
750-
iState = eReadingBody;
834+
if (iIsChunked)
835+
{
836+
iState = eReadingChunkLength;
837+
iChunkLength = 0;
838+
}
839+
else
840+
{
841+
iState = eReadingBody;
842+
}
751843
}
752844
break;
753845
default:
@@ -760,6 +852,7 @@ int HttpClient::readHeader()
760852
// We've got to the end of this line, start processing again
761853
iState = eStatusCodeRead;
762854
iContentLengthPtr = kContentLengthPrefix;
855+
iTransferEncodingChunkedPtr = kTransferEncodingChunked;
763856
}
764857
// And return the character read to whoever wants it
765858
return c;

‎src/HttpClient.h

Copy file name to clipboardExpand all lines: src/HttpClient.h
+19-3Lines changed: 19 additions & 3 deletions
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
{
@@ -254,7 +256,7 @@ class HttpClient : public Client
254256
/** Test whether all of the response headers have been consumed.
255257
@return true if we are now processing the response body, else false
256258
*/
257-
bool endOfHeadersReached() { return (iState == eReadingBody); };
259+
bool endOfHeadersReached();
258260

259261
/** Test whether the end of the body has been reached.
260262
Only works if the Content-Length header was returned by the server
@@ -272,6 +274,11 @@ class HttpClient : public Client
272274
*/
273275
int contentLength();
274276

277+
/** Returns if the response body is chunked
278+
@return true if response body is chunked, false otherwise
279+
*/
280+
int isResponseChunked() { return iIsChunked; }
281+
275282
/** Return the response body as a String
276283
Also skips response headers if they have not been read already
277284
MUST be called after responseStatusCode()
@@ -293,7 +300,7 @@ class HttpClient : public Client
293300
virtual size_t write(uint8_t aByte) { if (iState < eRequestSent) { finishHeaders(); }; return iClient-> write(aByte); };
294301
virtual size_t write(const uint8_t *aBuffer, size_t aSize) { if (iState < eRequestSent) { finishHeaders(); }; return iClient->write(aBuffer, aSize); };
295302
// Inherited from Stream
296-
virtual int available() { return iClient->available(); };
303+
virtual int available();
297304
/** Read the next byte from the server.
298305
@return Byte read or -1 if there are no bytes available.
299306
*/
@@ -339,6 +346,7 @@ class HttpClient : public Client
339346
// processing)
340347
static const int kHttpResponseTimeout = 30*1000;
341348
static const char* kContentLengthPrefix;
349+
static const char* kTransferEncodingChunked;
342350
typedef enum {
343351
eIdle,
344352
eRequestStarted,
@@ -348,7 +356,9 @@ class HttpClient : public Client
348356
eReadingContentLength,
349357
eSkipToEndOfHeader,
350358
eLineStartingCRFound,
351-
eReadingBody
359+
eReadingBody,
360+
eReadingChunkLength,
361+
eReadingBodyChunk
352362
} tHttpState;
353363
// Client we're using
354364
Client* iClient;
@@ -367,6 +377,12 @@ class HttpClient : public Client
367377
int iBodyLengthConsumed;
368378
// How far through a Content-Length header prefix we are
369379
const char* iContentLengthPtr;
380+
// How far through a Transfer-Encoding chunked header we are
381+
const char* iTransferEncodingChunkedPtr;
382+
// Stores if the response body is chunked
383+
bool iIsChunked;
384+
// Stores the value of the current chunk length, if present
385+
int iChunkLength;
370386
uint32_t iHttpResponseTimeout;
371387
bool iConnectionClose;
372388
bool iSendDefaultRequestHeaders;

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.