mirror of
https://github.com/arduino-libraries/ArduinoHttpClient.git
synced 2025-06-07 07:42:01 +03:00
Merge pull request #21 from sandeepmistry/chunked-response-body-support
Add support for chunked response bodies
This commit is contained in:
commit
765f2db637
@ -21,6 +21,51 @@ function serverStart() {
|
||||
console.log('Server listening on port '+ port);
|
||||
}
|
||||
|
||||
app.get('/chunked', function(request, response) {
|
||||
response.write('\n');
|
||||
response.write(' `:;;;,` .:;;:. \n');
|
||||
response.write(' .;;;;;;;;;;;` :;;;;;;;;;;: TM \n');
|
||||
response.write(' `;;;;;;;;;;;;;;;` :;;;;;;;;;;;;;;; \n');
|
||||
response.write(' :;;;;;;;;;;;;;;;;;; `;;;;;;;;;;;;;;;;;; \n');
|
||||
response.write(' ;;;;;;;;;;;;;;;;;;;;; .;;;;;;;;;;;;;;;;;;;; \n');
|
||||
response.write(' ;;;;;;;;:` `;;;;;;;;; ,;;;;;;;;.` .;;;;;;;; \n');
|
||||
response.write(' .;;;;;;, :;;;;;;; .;;;;;;; ;;;;;;; \n');
|
||||
response.write(' ;;;;;; ;;;;;;; ;;;;;;, ;;;;;;. \n');
|
||||
response.write(' ,;;;;; ;;;;;;.;;;;;;` ;;;;;; \n');
|
||||
response.write(' ;;;;;. ;;;;;;;;;;;` ``` ;;;;;`\n');
|
||||
response.write(' ;;;;; ;;;;;;;;;, ;;; .;;;;;\n');
|
||||
response.write('`;;;;: `;;;;;;;; ;;; ;;;;;\n');
|
||||
response.write(',;;;;` `,,,,,,,, ;;;;;;; .,,;;;,,, ;;;;;\n');
|
||||
response.write(':;;;;` .;;;;;;;; ;;;;;, :;;;;;;;; ;;;;;\n');
|
||||
response.write(':;;;;` .;;;;;;;; `;;;;;; :;;;;;;;; ;;;;;\n');
|
||||
response.write('.;;;;. ;;;;;;;. ;;; ;;;;;\n');
|
||||
response.write(' ;;;;; ;;;;;;;;; ;;; ;;;;;\n');
|
||||
response.write(' ;;;;; .;;;;;;;;;; ;;; ;;;;;,\n');
|
||||
response.write(' ;;;;;; `;;;;;;;;;;;; ;;;;; \n');
|
||||
response.write(' `;;;;;, .;;;;;; ;;;;;;; ;;;;;; \n');
|
||||
response.write(' ;;;;;;: :;;;;;;. ;;;;;;; ;;;;;; \n');
|
||||
response.write(' ;;;;;;;` .;;;;;;;, ;;;;;;;; ;;;;;;;: \n');
|
||||
response.write(' ;;;;;;;;;:,:;;;;;;;;;: ;;;;;;;;;;:,;;;;;;;;;; \n');
|
||||
response.write(' `;;;;;;;;;;;;;;;;;;;. ;;;;;;;;;;;;;;;;;;;; \n');
|
||||
response.write(' ;;;;;;;;;;;;;;;;; :;;;;;;;;;;;;;;;;: \n');
|
||||
response.write(' ,;;;;;;;;;;;;;, ;;;;;;;;;;;;;; \n');
|
||||
response.write(' .;;;;;;;;;` ,;;;;;;;;: \n');
|
||||
response.write(' \n');
|
||||
response.write(' \n');
|
||||
response.write(' \n');
|
||||
response.write(' \n');
|
||||
response.write(' ;;; ;;;;;` ;;;;: .;; ;; ,;;;;;, ;;. `;, ;;;; \n');
|
||||
response.write(' ;;; ;;:;;; ;;;;;; .;; ;; ,;;;;;: ;;; `;, ;;;:;; \n');
|
||||
response.write(' ,;:; ;; ;; ;; ;; .;; ;; ,;, ;;;,`;, ;; ;; \n');
|
||||
response.write(' ;; ;: ;; ;; ;; ;; .;; ;; ,;, ;;;;`;, ;; ;;. \n');
|
||||
response.write(' ;: ;; ;;;;;: ;; ;; .;; ;; ,;, ;;`;;;, ;; ;;` \n');
|
||||
response.write(' ,;;;;; ;;`;; ;; ;; .;; ;; ,;, ;; ;;;, ;; ;; \n');
|
||||
response.write(' ;; ,;, ;; .;; ;;;;;: ;;;;;: ,;;;;;: ;; ;;, ;;;;;; \n');
|
||||
response.write(' ;; ;; ;; ;;` ;;;;. `;;;: ,;;;;;, ;; ;;, ;;;; \n');
|
||||
response.write('\n');
|
||||
response.end();
|
||||
});
|
||||
|
||||
// this is the POST handler:
|
||||
app.all('/*', function (request, response) {
|
||||
console.log('Got a ' + request.method + ' request');
|
||||
|
@ -31,6 +31,7 @@ endOfHeadersReached KEYWORD2
|
||||
endOfBodyReached KEYWORD2
|
||||
completed KEYWORD2
|
||||
contentLength KEYWORD2
|
||||
isResponseChunked KEYWORD2
|
||||
connectionKeepAlive KEYWORD2
|
||||
noDefaultRequestHeaders KEYWORD2
|
||||
headerAvailable KEYWORD2
|
||||
|
@ -8,6 +8,7 @@
|
||||
// Initialize constants
|
||||
const char* HttpClient::kUserAgent = "Arduino/2.2.0";
|
||||
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)
|
||||
: iClient(&aClient), iServerName(aServerName), iServerAddress(), iServerPort(aServerPort),
|
||||
@ -35,6 +36,9 @@ void HttpClient::resetState()
|
||||
iContentLength = kNoContentLengthHeader;
|
||||
iBodyLengthConsumed = 0;
|
||||
iContentLengthPtr = kContentLengthPrefix;
|
||||
iTransferEncodingChunkedPtr = kTransferEncodingChunked;
|
||||
iIsChunked = false;
|
||||
iChunkLength = 0;
|
||||
iHttpResponseTimeout = kHttpResponseTimeout;
|
||||
}
|
||||
|
||||
@ -62,7 +66,7 @@ void HttpClient::beginRequest()
|
||||
int HttpClient::startRequest(const char* aURLPath, const char* aHttpMethod,
|
||||
const char* aContentType, int aContentLength, const byte aBody[])
|
||||
{
|
||||
if (iState == eReadingBody)
|
||||
if (iState == eReadingBody || iState == eReadingChunkLength || iState == eReadingBodyChunk)
|
||||
{
|
||||
flushClientRx();
|
||||
|
||||
@ -533,6 +537,11 @@ int HttpClient::skipResponseHeaders()
|
||||
}
|
||||
}
|
||||
|
||||
bool HttpClient::endOfHeadersReached()
|
||||
{
|
||||
return (iState == eReadingBody || iState == eReadingChunkLength || iState == eReadingBodyChunk);
|
||||
};
|
||||
|
||||
int HttpClient::contentLength()
|
||||
{
|
||||
// skip the response headers, if they haven't been read already
|
||||
@ -595,8 +604,62 @@ bool HttpClient::endOfBodyReached()
|
||||
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()
|
||||
{
|
||||
if (iIsChunked && !available())
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
int ret = iClient->read();
|
||||
if (ret >= 0)
|
||||
{
|
||||
@ -606,6 +669,16 @@ int HttpClient::read()
|
||||
// So keep track of how many bytes are left
|
||||
iBodyLengthConsumed++;
|
||||
}
|
||||
|
||||
if (iState == eReadingBodyChunk)
|
||||
{
|
||||
iChunkLength--;
|
||||
|
||||
if (iChunkLength == 0)
|
||||
{
|
||||
iState = eReadingChunkLength;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
@ -719,7 +792,18 @@ int HttpClient::readHeader()
|
||||
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
|
||||
// the end of the headers
|
||||
@ -727,7 +811,7 @@ int HttpClient::readHeader()
|
||||
}
|
||||
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;
|
||||
}
|
||||
break;
|
||||
@ -746,9 +830,17 @@ int HttpClient::readHeader()
|
||||
break;
|
||||
case eLineStartingCRFound:
|
||||
if (c == '\n')
|
||||
{
|
||||
if (iIsChunked)
|
||||
{
|
||||
iState = eReadingChunkLength;
|
||||
iChunkLength = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
iState = eReadingBody;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// We're just waiting for the end of the line now
|
||||
@ -760,6 +852,7 @@ int HttpClient::readHeader()
|
||||
// We've got to the end of this line, start processing again
|
||||
iState = eStatusCodeRead;
|
||||
iContentLengthPtr = kContentLengthPrefix;
|
||||
iTransferEncodingChunkedPtr = kTransferEncodingChunked;
|
||||
}
|
||||
// And return the character read to whoever wants it
|
||||
return c;
|
||||
|
@ -34,7 +34,9 @@ static const int HTTP_ERROR_INVALID_RESPONSE =-4;
|
||||
#define HTTP_HEADER_CONTENT_LENGTH "Content-Length"
|
||||
#define HTTP_HEADER_CONTENT_TYPE "Content-Type"
|
||||
#define HTTP_HEADER_CONNECTION "Connection"
|
||||
#define HTTP_HEADER_TRANSFER_ENCODING "Transfer-Encoding"
|
||||
#define HTTP_HEADER_USER_AGENT "User-Agent"
|
||||
#define HTTP_HEADER_VALUE_CHUNKED "chunked"
|
||||
|
||||
class HttpClient : public Client
|
||||
{
|
||||
@ -254,7 +256,7 @@ public:
|
||||
/** Test whether all of the response headers have been consumed.
|
||||
@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.
|
||||
Only works if the Content-Length header was returned by the server
|
||||
@ -272,6 +274,11 @@ public:
|
||||
*/
|
||||
int contentLength();
|
||||
|
||||
/** Returns if the response body is chunked
|
||||
@return true if response body is chunked, false otherwise
|
||||
*/
|
||||
int isResponseChunked() { return iIsChunked; }
|
||||
|
||||
/** Return the response body as a String
|
||||
Also skips response headers if they have not been read already
|
||||
MUST be called after responseStatusCode()
|
||||
@ -293,7 +300,7 @@ public:
|
||||
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); };
|
||||
// Inherited from Stream
|
||||
virtual int available() { return iClient->available(); };
|
||||
virtual int available();
|
||||
/** Read the next byte from the server.
|
||||
@return Byte read or -1 if there are no bytes available.
|
||||
*/
|
||||
@ -339,6 +346,7 @@ protected:
|
||||
// processing)
|
||||
static const int kHttpResponseTimeout = 30*1000;
|
||||
static const char* kContentLengthPrefix;
|
||||
static const char* kTransferEncodingChunked;
|
||||
typedef enum {
|
||||
eIdle,
|
||||
eRequestStarted,
|
||||
@ -348,7 +356,9 @@ protected:
|
||||
eReadingContentLength,
|
||||
eSkipToEndOfHeader,
|
||||
eLineStartingCRFound,
|
||||
eReadingBody
|
||||
eReadingBody,
|
||||
eReadingChunkLength,
|
||||
eReadingBodyChunk
|
||||
} tHttpState;
|
||||
// Client we're using
|
||||
Client* iClient;
|
||||
@ -367,6 +377,12 @@ protected:
|
||||
int iBodyLengthConsumed;
|
||||
// How far through a Content-Length header prefix we are
|
||||
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;
|
||||
bool iConnectionClose;
|
||||
bool iSendDefaultRequestHeaders;
|
||||
|
Loading…
x
Reference in New Issue
Block a user