diff --git a/libraries/ESP8266HTTPClient/src/ESP8266HTTPClient.cpp b/libraries/ESP8266HTTPClient/src/ESP8266HTTPClient.cpp index 89da711f32..4fd336c4db 100644 --- a/libraries/ESP8266HTTPClient/src/ESP8266HTTPClient.cpp +++ b/libraries/ESP8266HTTPClient/src/ESP8266HTTPClient.cpp @@ -58,6 +58,9 @@ void HTTPClient::clear() _size = -1; _headers.clear(); _location.clear(); + _chunkHeader.clear(); + _chunkLen = 0; + _transferEncoding = HTTPC_TE_IDENTITY; _payload.reset(); } @@ -674,18 +677,11 @@ int HTTPClient::writeToPrint(Print * print) if(!connected()) { return returnError(HTTPC_ERROR_CONNECTION_LOST); } - String chunkHeader = _client->readStringUntil('\n'); - - if(chunkHeader.length() <= 0) { + if(!readChunkHeader()) { return returnError(HTTPC_ERROR_READ_TIMEOUT); } - - chunkHeader.trim(); // remove \r - - // read size of chunk - len = (uint32_t) strtol((const char *) chunkHeader.c_str(), NULL, 16); - size += len; - DEBUG_HTTPCLIENT("[HTTP-Client] read chunk len: %d\n", len); + size += _chunkLen; + DEBUG_HTTPCLIENT("[HTTP-Client] read chunk len: %d\n", _chunkLen); // data left? if(len > 0) { @@ -710,9 +706,7 @@ int HTTPClient::writeToPrint(Print * print) } // read trailing \r\n at the end of the chunk - char buf[2]; - auto trailing_seq_len = _client->readBytes((uint8_t*)buf, 2); - if (trailing_seq_len != 2 || buf[0] != '\r' || buf[1] != '\n') { + if (!readChunkTrailer()) { return returnError(HTTPC_ERROR_READ_TIMEOUT); } @@ -785,6 +779,142 @@ String HTTPClient::errorToString(int error) } } +/** + * write a byte to HTTP connection stream + * @param b uint8_t byte to be written + * @return number of bytes written + */ +size_t HTTPClient::write(uint8_t b) +{ + return write(&b, sizeof(b)); +} + +/** + * write a byte array to HTTP connection stream + * @param buffer uint8_t * byte array to be written + * @param size size_t byte array size + * @return number of bytes written + */ +size_t HTTPClient::write(const uint8_t *buffer, size_t size) +{ + if (!connected()) { + return 0; + } + if (_transferEncoding == HTTPC_TE_CHUNKED) { + int written; + + if (size == 0) { + return size; + } + if ((_chunkLen != 0) && (_chunkOffset == _chunkLen)) { + if (_client->write("\r\n") == 2) { + _chunkLen = 0; + } else { + return 0; + } + } + if (_chunkLen == 0) { + String header = String(size, 16) + "\r\n"; + + if (_client->write(header.c_str()) != header.length()) { + return 0; + } + _chunkLen = size; + _chunkOffset = 0; + } + if (size > _chunkLen - _chunkOffset) { + size = _chunkLen - _chunkOffset; + } + written = _client->write(buffer, size); + _chunkOffset += written; + return written; + } + else { + return _client->write(buffer, size); + } +} + +/** + * retrieve number of bytes available to be read from HTTP connection stream + * @return number of available bytes + */ +int HTTPClient::available() +{ + if (!connected()) { + return 0; + } + if (_transferEncoding == HTTPC_TE_IDENTITY) { + return _client->available(); + } else if(_transferEncoding == HTTPC_TE_CHUNKED) { + if ((_chunkLen == 0) && !readChunkHeader(false)) { + return 0; + } + if (_chunkLen > 0) { + if (_chunkOffset < _chunkLen) { + unsigned int available = (unsigned int) _client->available(); + if (available < _chunkLen - _chunkOffset) { + return available; + } else { + return (_chunkLen - _chunkOffset); + } + } else { + readChunkTrailer(false); + } + } + return 0; + } else { + return 0; + } +} + +/** + * read a byte from HTTP connection stream + * @return byte read, or -1 if no bytes could be read + */ +int HTTPClient::read() +{ + if (!connected()) { + return -1; + } + if (_transferEncoding == HTTPC_TE_IDENTITY) { + return _client->read(); + } else if(_transferEncoding == HTTPC_TE_CHUNKED) { + while (true) { + if ((_chunkLen == 0) && !readChunkHeader()) { + return -1; + } + if (_chunkLen > 0) { + if (_chunkOffset < _chunkLen) { + int c = _client->read(); + + if (c >= 0) { + _chunkOffset++; + } + return c; + } else if (!readChunkTrailer()) { + return -1; + } + } else { + return -1; + } + } + } else { + return -1; + } +} + +/** + * retrieve next byte available to be read from HTTP connection stream + * @return byte available to be read, or -1 if no bytes can be read + */ +int HTTPClient::peek() +{ + if (!available()) { + return -1; + } + return _client->peek(); +} + /** * adds Header to the request * @param name @@ -816,9 +946,14 @@ void HTTPClient::addHeader(const String& name, const String& value, bool first, headerLine += "\r\n"; if (first) { _headers = headerLine + _headers; + _transferEncoding = HTTPC_TE_IDENTITY; } else { _headers += headerLine; } + if (name.equalsIgnoreCase(F("Transfer-Encoding")) && + value.equalsIgnoreCase(F("chunked"))) { + _transferEncoding = HTTPC_TE_CHUNKED; + } } } @@ -972,6 +1107,27 @@ bool HTTPClient::sendHeader(const char * type) return StreamConstPtr(header).sendAll(_client.get()) == header.length(); } +/** + * sends HTTP request header + * @param type (GET, POST, ...) + * @return status + */ +bool HTTPClient::endRequest(void) +{ + if (!connected()) { + return false; + } + if (_transferEncoding == HTTPC_TE_CHUNKED) { + if ((_chunkLen != 0) && (_client->write("\r\n") != 2)) { + return false; + } + return (_client->write("0\r\n\r\n") == 5); + } + else { + return true; + } +} + /** * reads the response from the server * @return int http code @@ -989,7 +1145,6 @@ int HTTPClient::handleHeaderResponse() String transferEncoding; - _transferEncoding = HTTPC_TE_IDENTITY; unsigned long lastDataTime = millis(); while(connected()) { @@ -1103,3 +1258,60 @@ int HTTPClient::returnError(int error) } return error; } + + +/** + * Read header of next chunk in HTTP response using chunked encoding + * @param blocking bool whether this method is allowed to block + * @return boolean value indicating whether a complete header could be read + */ +bool HTTPClient::readChunkHeader(bool blocking) +{ + if (blocking) { + _chunkHeader += _client->readStringUntil('\n'); + if (_chunkHeader.length() == 0) { + return false; + } + } else { + while (_client->available() && !_chunkHeader.endsWith("\n")) { + _chunkHeader += (char) _client->read(); + } + if (!_chunkHeader.endsWith("\n")) { + return false; + } + } + + // read size of chunk + _chunkLen = (uint32_t) strtol((const char *) _chunkHeader.c_str(), NULL, + 16); + + _chunkOffset = 0; + return true; +} + +/** + * Read trailer of current chunk in HTTP response using chunked encoding + * @param blocking bool whether this method is allowed to block + * @return boolean value indicating whether the complete trailer could be read + */ +bool HTTPClient::readChunkTrailer(bool blocking) +{ + uint8_t buf[2]; + + if (blocking || (_client->available() >= 2)) { + auto trailing_seq_len = _client->readBytes((uint8_t *) buf, 2); + + if (trailing_seq_len != 2 || buf[0] != '\r' || buf[1] != '\n') { + return false; + } else { + /* Clear _chunkHeader to indicate that the current chunk has been + * completely read, and reset _chunkLen to indicate that the next + * chunk header has not been read yet. */ + _chunkHeader.clear(); + _chunkLen = 0; + return true; + } + } else { + return false; + } +} diff --git a/libraries/ESP8266HTTPClient/src/ESP8266HTTPClient.h b/libraries/ESP8266HTTPClient/src/ESP8266HTTPClient.h index 9e3aef9ecd..a7b1110c70 100644 --- a/libraries/ESP8266HTTPClient/src/ESP8266HTTPClient.h +++ b/libraries/ESP8266HTTPClient/src/ESP8266HTTPClient.h @@ -149,7 +149,9 @@ typedef enum { class TransportTraits; typedef std::unique_ptr TransportTraitsPtr; -class HTTPClient +class StreamString; + +class HTTPClient : public Stream { public: HTTPClient() = default; @@ -220,6 +222,12 @@ class HTTPClient const String& getString(void); static String errorToString(int error); + size_t write(uint8_t b); + size_t write(const uint8_t *buffer, size_t size); + int available(); + int read(); + int peek(); + protected: struct RequestArgument { String key; @@ -232,8 +240,10 @@ class HTTPClient int returnError(int error); bool connect(void); bool sendHeader(const char * type); + bool endRequest(void); int handleHeaderResponse(); - int writeToStreamDataBlock(Stream * stream, int len); + bool readChunkHeader(bool blocking = true); + bool readChunkTrailer(bool blocking = true); // The common pattern to use the class is to // { @@ -271,6 +281,9 @@ class HTTPClient uint16_t _redirectLimit = 10; String _location; transferEncoding_t _transferEncoding = HTTPC_TE_IDENTITY; + String _chunkHeader; + uint32_t _chunkLen = 0; + uint32_t _chunkOffset = 0; std::unique_ptr _payload; };