diff --git a/cores/esp8266/Stream.h b/cores/esp8266/Stream.h index f7a010153..f39bb423f 100644 --- a/cores/esp8266/Stream.h +++ b/cores/esp8266/Stream.h @@ -167,25 +167,49 @@ class Stream: public Print { // When result is 0 or less than requested maxLen, Print::getLastSend() // contains an error reason. +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + // transfers already buffered / immediately available data (no timeout) // returns number of transferred bytes - size_t sendAvailable (Print* to) { return sendGeneric(to, -1, -1, oneShotMs::alwaysExpired); } - size_t sendAvailable (Print& to) { return sendAvailable(&to); } + [[deprecated]] size_t sendAvailable (Print* to) { return sendGeneric(to, -1, -1, oneShotMs::alwaysExpired); } + [[deprecated]] size_t sendAvailable (Print& to) { return sendAvailable(&to); } // transfers data until timeout // returns number of transferred bytes - size_t sendAll (Print* to, const oneShotMs::timeType timeoutMs = oneShotMs::neverExpires) { return sendGeneric(to, -1, -1, timeoutMs); } - size_t sendAll (Print& to, const oneShotMs::timeType timeoutMs = oneShotMs::neverExpires) { return sendAll(&to, timeoutMs); } + [[deprecated]] size_t sendAll (Print* to, const oneShotMs::timeType timeoutMs = oneShotMs::neverExpires) { return sendGeneric(to, -1, -1, timeoutMs); } + [[deprecated]] size_t sendAll (Print& to, const oneShotMs::timeType timeoutMs = oneShotMs::neverExpires) { return sendAll(&to, timeoutMs); } // transfers data until a char is encountered (the char is swallowed but not transferred) with timeout // returns number of transferred bytes - size_t sendUntil (Print* to, const int readUntilChar, const oneShotMs::timeType timeoutMs = oneShotMs::neverExpires) { return sendGeneric(to, -1, readUntilChar, timeoutMs); } - size_t sendUntil (Print& to, const int readUntilChar, const oneShotMs::timeType timeoutMs = oneShotMs::neverExpires) { return sendUntil(&to, readUntilChar, timeoutMs); } + [[deprecated]] size_t sendUntil (Print* to, const int readUntilChar, const oneShotMs::timeType timeoutMs = oneShotMs::neverExpires) { return sendGeneric(to, -1, readUntilChar, timeoutMs); } + [[deprecated]] size_t sendUntil (Print& to, const int readUntilChar, const oneShotMs::timeType timeoutMs = oneShotMs::neverExpires) { return sendUntil(&to, readUntilChar, timeoutMs); } // transfers data until requested size or timeout // returns number of transferred bytes - size_t sendSize (Print* to, const ssize_t maxLen, const oneShotMs::timeType timeoutMs = oneShotMs::neverExpires) { return sendGeneric(to, maxLen, -1, timeoutMs); } - size_t sendSize (Print& to, const ssize_t maxLen, const oneShotMs::timeType timeoutMs = oneShotMs::neverExpires) { return sendSize(&to, maxLen, timeoutMs); } + [[deprecated]] size_t sendSize (Print* to, const ssize_t maxLen, const oneShotMs::timeType timeoutMs = oneShotMs::neverExpires) { return sendGeneric(to, maxLen, -1, timeoutMs); } + [[deprecated]] size_t sendSize (Print& to, const ssize_t maxLen, const oneShotMs::timeType timeoutMs = oneShotMs::neverExpires) { return sendSize(&to, maxLen, timeoutMs); } + +#pragma GCC diagnostic pop + + // transfers already buffered / immediately available data (no timeout) + // returns number of transferred bytes + size_t sendAvailable (Stream* to) { return sendGeneric(to, -1, -1, oneShotMs::alwaysExpired); } + size_t sendAvailable (Stream& to) { return sendAvailable(&to); } + + // transfers data until timeout + // returns number of transferred bytes + size_t sendAll (Stream* to, const oneShotMs::timeType timeoutMs = oneShotMs::neverExpires) { return sendGeneric(to, -1, -1, timeoutMs); } + size_t sendAll (Stream& to, const oneShotMs::timeType timeoutMs = oneShotMs::neverExpires) { return sendAll(&to, timeoutMs); } + + // transfers data until a char is encountered (the char is swallowed but not transferred) with timeout + // returns number of transferred bytes + size_t sendUntil (Stream* to, const int readUntilChar, const oneShotMs::timeType timeoutMs = oneShotMs::neverExpires) { return sendGeneric(to, -1, readUntilChar, timeoutMs); } + size_t sendUntil (Stream& to, const int readUntilChar, const oneShotMs::timeType timeoutMs = oneShotMs::neverExpires) { return sendUntil(&to, readUntilChar, timeoutMs); } + + // transfers data until requested size or timeout + // returns number of transferred bytes + size_t sendSize (Stream* to, const ssize_t maxLen, const oneShotMs::timeType timeoutMs = oneShotMs::neverExpires) { return sendGeneric(to, maxLen, -1, timeoutMs); } + size_t sendSize (Stream& to, const ssize_t maxLen, const oneShotMs::timeType timeoutMs = oneShotMs::neverExpires) { return sendSize(&to, maxLen, timeoutMs); } // remaining size (-1 by default = unknown) virtual ssize_t streamRemaining () { return -1; } @@ -202,11 +226,17 @@ class Stream: public Print { Report getLastSendReport () const { return _sendReport; } protected: + [[deprecated]] size_t sendGeneric (Print* to, const ssize_t len = -1, const int readUntilChar = -1, oneShotMs::timeType timeoutMs = oneShotMs::neverExpires /* neverExpires=>getTimeout() */); + size_t sendGeneric (Stream* to, + const ssize_t len = -1, + const int readUntilChar = -1, + oneShotMs::timeType timeoutMs = oneShotMs::neverExpires /* neverExpires=>getTimeout() */); + size_t SendGenericPeekBuffer(Print* to, const ssize_t len, const int readUntilChar, const oneShotMs::timeType timeoutMs); size_t SendGenericRegularUntil(Print* to, const ssize_t len, const int readUntilChar, const oneShotMs::timeType timeoutMs); size_t SendGenericRegular(Print* to, const ssize_t len, const oneShotMs::timeType timeoutMs); diff --git a/cores/esp8266/StreamSend.cpp b/cores/esp8266/StreamSend.cpp index bf07f397b..b46d1c156 100644 --- a/cores/esp8266/StreamSend.cpp +++ b/cores/esp8266/StreamSend.cpp @@ -22,9 +22,17 @@ #include #include -size_t Stream::sendGeneric(Print* to, const ssize_t len, const int readUntilChar, +size_t Stream::sendGeneric(Stream* to, const ssize_t len, const int readUntilChar, const esp8266::polledTimeout::oneShotFastMs::timeType timeoutMs) { + // "neverExpires (default, impossible)" is translated to default timeout + esp8266::polledTimeout::oneShotFastMs::timeType inputTimeoutMs + = timeoutMs >= esp8266::polledTimeout::oneShotFastMs::neverExpires ? getTimeout() + : timeoutMs; + + esp8266::polledTimeout::oneShotFastMs::timeType mainTimeoutMs = std::max( + inputTimeoutMs, (esp8266::polledTimeout::oneShotFastMs::timeType)to->getTimeout()); + setReport(Report::Success); if (len == 0) @@ -43,25 +51,60 @@ size_t Stream::sendGeneric(Print* to, const ssize_t len, const int readUntilChar if (hasPeekBufferAPI()) { - return SendGenericPeekBuffer(to, len, readUntilChar, timeoutMs); + return SendGenericPeekBuffer(to, len, readUntilChar, mainTimeoutMs); } if (readUntilChar >= 0) { - return SendGenericRegularUntil(to, len, readUntilChar, timeoutMs); + return SendGenericRegularUntil(to, len, readUntilChar, mainTimeoutMs); } - return SendGenericRegular(to, len, timeoutMs); + return SendGenericRegular(to, len, mainTimeoutMs); +} + +size_t Stream::sendGeneric(Print* to, const ssize_t len, const int readUntilChar, + const esp8266::polledTimeout::oneShotFastMs::timeType timeoutMs) +{ + // "neverExpires (default, impossible)" is translated to default timeout + esp8266::polledTimeout::oneShotFastMs::timeType inputTimeoutMs + = timeoutMs >= esp8266::polledTimeout::oneShotFastMs::neverExpires ? getTimeout() + : timeoutMs; + + setReport(Report::Success); + + if (len == 0) + { + return 0; // conveniently avoids timeout for no requested data + } + + // There are two timeouts: + // - read (network, serial, ...) + // - write (network, serial, ...) + // However + // - getTimeout() is for reading only + // - there is no getOutputTimeout() api + // So we use getTimeout() for both, + // (also when inputCanTimeout() is false) + + if (hasPeekBufferAPI()) + { + return SendGenericPeekBuffer(to, len, readUntilChar, inputTimeoutMs); + } + + if (readUntilChar >= 0) + { + return SendGenericRegularUntil(to, len, readUntilChar, inputTimeoutMs); + } + + return SendGenericRegular(to, len, inputTimeoutMs); } size_t Stream::SendGenericPeekBuffer(Print* to, const ssize_t len, const int readUntilChar, const esp8266::polledTimeout::oneShotFastMs::timeType timeoutMs) { - // "neverExpires (default, impossible)" is translated to default timeout - esp8266::polledTimeout::oneShotFastMs timedOut( - timeoutMs >= esp8266::polledTimeout::oneShotFastMs::neverExpires ? getTimeout() - : timeoutMs); + esp8266::polledTimeout::oneShotFastMs timedOut(timeoutMs); + // len==-1 => maxLen=0 <=> until starvation const size_t maxLen = std::max((ssize_t)0, len); size_t written = 0; @@ -152,10 +195,8 @@ Stream::SendGenericRegularUntil(Print* to, const ssize_t len, const int readUnti // regular Stream API // no other choice than reading byte by byte - // "neverExpires (default, impossible)" is translated to default timeout - esp8266::polledTimeout::oneShotFastMs timedOut( - timeoutMs >= esp8266::polledTimeout::oneShotFastMs::neverExpires ? getTimeout() - : timeoutMs); + esp8266::polledTimeout::oneShotFastMs timedOut(timeoutMs); + // len==-1 => maxLen=0 <=> until starvation const size_t maxLen = std::max((ssize_t)0, len); size_t written = 0; @@ -231,10 +272,8 @@ size_t Stream::SendGenericRegular(Print* to, const ssize_t len, // regular Stream API // use an intermediary buffer - // "neverExpires (default, impossible)" is translated to default timeout - esp8266::polledTimeout::oneShotFastMs timedOut( - timeoutMs >= esp8266::polledTimeout::oneShotFastMs::neverExpires ? getTimeout() - : timeoutMs); + esp8266::polledTimeout::oneShotFastMs timedOut(timeoutMs); + // len==-1 => maxLen=0 <=> until starvation const size_t maxLen = std::max((ssize_t)0, len); size_t written = 0; diff --git a/libraries/ESP8266HTTPClient/src/ESP8266HTTPClient.cpp b/libraries/ESP8266HTTPClient/src/ESP8266HTTPClient.cpp index 89da711f3..429fc7c23 100644 --- a/libraries/ESP8266HTTPClient/src/ESP8266HTTPClient.cpp +++ b/libraries/ESP8266HTTPClient/src/ESP8266HTTPClient.cpp @@ -39,7 +39,7 @@ static_assert(std::is_move_assignable_v, ""); static const char defaultUserAgentPstr[] PROGMEM = "ESP8266HTTPClient"; const String HTTPClient::defaultUserAgent = defaultUserAgentPstr; -static int StreamReportToHttpClientReport (Stream::Report streamSendError) +int HTTPClient::StreamReportToHttpClientReport (Stream::Report streamSendError) { switch (streamSendError) { @@ -627,105 +627,6 @@ WiFiClient* HTTPClient::getStreamPtr(void) return nullptr; } -/** - * write all message body / payload to Stream - * @param stream Stream * - * @return bytes written ( negative values are error codes ) - */ -int HTTPClient::writeToStream(Stream * stream) -{ - return writeToPrint(stream); -} - -/** - * write all message body / payload to Print - * @param print Print * - * @return bytes written ( negative values are error codes ) - */ -int HTTPClient::writeToPrint(Print * print) -{ - - if(!print) { - return returnError(HTTPC_ERROR_NO_STREAM); - } - - // Only return error if not connected and no data available, because otherwise ::getString() will return an error instead of an empty - // string when the server returned a http code 204 (no content) - if(!connected() && _transferEncoding != HTTPC_TE_IDENTITY && _size > 0) { - return returnError(HTTPC_ERROR_NOT_CONNECTED); - } - - // get length of document (is -1 when Server sends no Content-Length header) - int len = _size; - int ret = 0; - - if(_transferEncoding == HTTPC_TE_IDENTITY) { - // len < 0: transfer all of it, with timeout - // len >= 0: max:len, with timeout - ret = _client->sendSize(print, len); - - // do we have an error? - if(_client->getLastSendReport() != Stream::Report::Success) { - return returnError(StreamReportToHttpClientReport(_client->getLastSendReport())); - } - } else if(_transferEncoding == HTTPC_TE_CHUNKED) { - int size = 0; - while(1) { - if(!connected()) { - return returnError(HTTPC_ERROR_CONNECTION_LOST); - } - String chunkHeader = _client->readStringUntil('\n'); - - if(chunkHeader.length() <= 0) { - 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); - - // data left? - if(len > 0) { - // read len bytes with timeout - int r = _client->sendSize(print, len); - if (_client->getLastSendReport() != Stream::Report::Success) - // not all data transferred - return returnError(StreamReportToHttpClientReport(_client->getLastSendReport())); - ret += r; - } else { - - // if no length Header use global chunk size - if(_size <= 0) { - _size = size; - } - - // check if we have write all data out - if(ret != _size) { - return returnError(HTTPC_ERROR_STREAM_WRITE); - } - break; - } - - // 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') { - return returnError(HTTPC_ERROR_READ_TIMEOUT); - } - - esp_yield(); - } - } else { - return returnError(HTTPC_ERROR_ENCODING); - } - - disconnect(true); - return ret; -} - /** * return all payload as String (may need lot of ram or trigger out of memory!) * @return String diff --git a/libraries/ESP8266HTTPClient/src/ESP8266HTTPClient.h b/libraries/ESP8266HTTPClient/src/ESP8266HTTPClient.h index 9e3aef9ec..f510c02a1 100644 --- a/libraries/ESP8266HTTPClient/src/ESP8266HTTPClient.h +++ b/libraries/ESP8266HTTPClient/src/ESP8266HTTPClient.h @@ -215,8 +215,8 @@ public: WiFiClient& getStream(void); WiFiClient* getStreamPtr(void); - int writeToPrint(Print* print); - int writeToStream(Stream* stream); + template int writeToPrint(S* print) [[deprecated]] { return writeToStream(print); } + template int writeToStream(S* output); const String& getString(void); static String errorToString(int error); @@ -234,6 +234,7 @@ protected: bool sendHeader(const char * type); int handleHeaderResponse(); int writeToStreamDataBlock(Stream * stream, int len); + static int StreamReportToHttpClientReport (Stream::Report streamSendError); // The common pattern to use the class is to // { @@ -274,4 +275,93 @@ protected: std::unique_ptr _payload; }; +/** + * write all message body / payload to Stream + * @param output Print*(obsolete) / Stream* + * @return bytes written ( negative values are error codes ) + */ +template +int HTTPClient::writeToStream(S * output) +{ + if(!output) { + return returnError(HTTPC_ERROR_NO_STREAM); + } + + // Only return error if not connected and no data available, because otherwise ::getString() will return an error instead of an empty + // string when the server returned a http code 204 (no content) + if(!connected() && _transferEncoding != HTTPC_TE_IDENTITY && _size > 0) { + return returnError(HTTPC_ERROR_NOT_CONNECTED); + } + + // get length of document (is -1 when Server sends no Content-Length header) + int len = _size; + int ret = 0; + + if(_transferEncoding == HTTPC_TE_IDENTITY) { + // len < 0: transfer all of it, with timeout + // len >= 0: max:len, with timeout + ret = _client->sendSize(output, len); + + // do we have an error? + if(_client->getLastSendReport() != Stream::Report::Success) { + return returnError(StreamReportToHttpClientReport(_client->getLastSendReport())); + } + } else if(_transferEncoding == HTTPC_TE_CHUNKED) { + int size = 0; + while(1) { + if(!connected()) { + return returnError(HTTPC_ERROR_CONNECTION_LOST); + } + String chunkHeader = _client->readStringUntil('\n'); + + if(chunkHeader.length() <= 0) { + 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); + + // data left? + if(len > 0) { + // read len bytes with timeout + int r = _client->sendSize(output, len); + if (_client->getLastSendReport() != Stream::Report::Success) + // not all data transferred + return returnError(StreamReportToHttpClientReport(_client->getLastSendReport())); + ret += r; + } else { + + // if no length Header use global chunk size + if(_size <= 0) { + _size = size; + } + + // check if we have write all data out + if(ret != _size) { + return returnError(HTTPC_ERROR_STREAM_WRITE); + } + break; + } + + // 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') { + return returnError(HTTPC_ERROR_READ_TIMEOUT); + } + + esp_yield(); + } + } else { + return returnError(HTTPC_ERROR_ENCODING); + } + + disconnect(true); + return ret; +} + #endif /* ESP8266HTTPClient_H_ */