Skip to content

Commit eec286d

Browse files
HTTPClient: implement Stream class
It is now possible to stream HTTP request data without knowing in advance the data size (using chunked transfer encoding), and to stream HTTP response data (correctly decoded according to the transfer encoding method used by the server) without requiring an output Stream implementation. (The existing getStream() method in the HTTPClient class returns the underlying WiFiClient stream which is not aware of the HTTP transfer encoding.) When using chunked encoding in an HTTP request, each call to write() results in the transmission of a separate chunk. The setting of _transferEncoding to HTTPC_TE_IDENTITY in the handleHeaderResponse() method has been removed because _transferEncoding is now used for both transmission and reception, and is reset to HTTPC_TE_IDENTITY in the clear() method.
1 parent 69a869c commit eec286d

File tree

2 files changed

+173
-2
lines changed

2 files changed

+173
-2
lines changed

libraries/ESP8266HTTPClient/src/ESP8266HTTPClient.cpp

+164-1
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ void HTTPClient::clear()
141141
_location.clear();
142142
_chunkHeader.clear();
143143
_chunkLen = 0;
144+
_transferEncoding = HTTPC_TE_IDENTITY;
144145
_payload.reset();
145146
}
146147

@@ -1060,6 +1061,142 @@ String HTTPClient::errorToString(int error)
10601061
}
10611062
}
10621063

1064+
/**
1065+
* write a byte to HTTP connection stream
1066+
* @param b uint8_t byte to be written
1067+
* @return number of bytes written
1068+
*/
1069+
size_t HTTPClient::write(uint8_t b)
1070+
{
1071+
return write(&b, sizeof(b));
1072+
}
1073+
1074+
/**
1075+
* write a byte array to HTTP connection stream
1076+
* @param buffer uint8_t * byte array to be written
1077+
* @param size size_t byte array size
1078+
* @return number of bytes written
1079+
*/
1080+
size_t HTTPClient::write(const uint8_t *buffer, size_t size)
1081+
{
1082+
if (!connected()) {
1083+
return 0;
1084+
}
1085+
if (_transferEncoding == HTTPC_TE_CHUNKED) {
1086+
int written;
1087+
1088+
if (size == 0) {
1089+
return size;
1090+
}
1091+
if ((_chunkLen != 0) && (_chunkOffset == _chunkLen)) {
1092+
if (_client->write("\r\n") == 2) {
1093+
_chunkLen = 0;
1094+
} else {
1095+
return 0;
1096+
}
1097+
}
1098+
if (_chunkLen == 0) {
1099+
String header = String(size, 16) + "\r\n";
1100+
1101+
if (_client->write(header.c_str()) != header.length()) {
1102+
return 0;
1103+
}
1104+
_chunkLen = size;
1105+
_chunkOffset = 0;
1106+
}
1107+
if (size > _chunkLen - _chunkOffset) {
1108+
size = _chunkLen - _chunkOffset;
1109+
}
1110+
written = _client->write(buffer, size);
1111+
_chunkOffset += written;
1112+
return written;
1113+
}
1114+
else {
1115+
return _client->write(buffer, size);
1116+
}
1117+
}
1118+
1119+
/**
1120+
* retrieve number of bytes available to be read from HTTP connection stream
1121+
* @return number of available bytes
1122+
*/
1123+
int HTTPClient::available()
1124+
{
1125+
if (!connected()) {
1126+
return 0;
1127+
}
1128+
if (_transferEncoding == HTTPC_TE_IDENTITY) {
1129+
return _client->available();
1130+
} else if(_transferEncoding == HTTPC_TE_CHUNKED) {
1131+
if ((_chunkLen == 0) && !readChunkHeader(false)) {
1132+
return 0;
1133+
}
1134+
if (_chunkLen > 0) {
1135+
if (_chunkOffset < _chunkLen) {
1136+
unsigned int available = (unsigned int) _client->available();
1137+
if (available < _chunkLen - _chunkOffset) {
1138+
return available;
1139+
} else {
1140+
return (_chunkLen - _chunkOffset);
1141+
}
1142+
} else {
1143+
readChunkTrailer(false);
1144+
}
1145+
}
1146+
return 0;
1147+
} else {
1148+
return 0;
1149+
}
1150+
}
1151+
1152+
/**
1153+
* read a byte from HTTP connection stream
1154+
* @return byte read, or -1 if no bytes could be read
1155+
*/
1156+
int HTTPClient::read()
1157+
{
1158+
if (!connected()) {
1159+
return -1;
1160+
}
1161+
if (_transferEncoding == HTTPC_TE_IDENTITY) {
1162+
return _client->read();
1163+
} else if(_transferEncoding == HTTPC_TE_CHUNKED) {
1164+
while (true) {
1165+
if ((_chunkLen == 0) && !readChunkHeader()) {
1166+
return -1;
1167+
}
1168+
if (_chunkLen > 0) {
1169+
if (_chunkOffset < _chunkLen) {
1170+
int c = _client->read();
1171+
1172+
if (c >= 0) {
1173+
_chunkOffset++;
1174+
}
1175+
return c;
1176+
} else if (!readChunkTrailer()) {
1177+
return -1;
1178+
}
1179+
} else {
1180+
return -1;
1181+
}
1182+
}
1183+
} else {
1184+
return -1;
1185+
}
1186+
}
1187+
1188+
/**
1189+
* retrieve next byte available to be read from HTTP connection stream
1190+
* @return byte available to be read, or -1 if no bytes can be read
1191+
*/
1192+
int HTTPClient::peek()
1193+
{
1194+
if (!available()) {
1195+
return -1;
1196+
}
1197+
return _client->peek();
1198+
}
1199+
10631200
/**
10641201
* adds Header to the request
10651202
* @param name
@@ -1091,9 +1228,14 @@ void HTTPClient::addHeader(const String& name, const String& value, bool first,
10911228
headerLine += "\r\n";
10921229
if (first) {
10931230
_headers = headerLine + _headers;
1231+
_transferEncoding = HTTPC_TE_IDENTITY;
10941232
} else {
10951233
_headers += headerLine;
10961234
}
1235+
if (name.equalsIgnoreCase(F("Transfer-Encoding")) &&
1236+
value.equalsIgnoreCase(F("chunked"))) {
1237+
_transferEncoding = HTTPC_TE_CHUNKED;
1238+
}
10971239
}
10981240
}
10991241

@@ -1269,6 +1411,27 @@ bool HTTPClient::sendHeader(const char * type)
12691411
return (_client->write((const uint8_t *) header.c_str(), header.length()) == header.length());
12701412
}
12711413

1414+
/**
1415+
* sends HTTP request header
1416+
* @param type (GET, POST, ...)
1417+
* @return status
1418+
*/
1419+
bool HTTPClient::endRequest(void)
1420+
{
1421+
if (!connected()) {
1422+
return false;
1423+
}
1424+
if (_transferEncoding == HTTPC_TE_CHUNKED) {
1425+
if ((_chunkLen != 0) && (_client->write("\r\n") != 2)) {
1426+
return false;
1427+
}
1428+
return (_client->write("0\r\n\r\n") == 5);
1429+
}
1430+
else {
1431+
return true;
1432+
}
1433+
}
1434+
12721435
/**
12731436
* reads the response from the server
12741437
* @return int http code
@@ -1286,7 +1449,6 @@ int HTTPClient::handleHeaderResponse()
12861449

12871450
String transferEncoding;
12881451

1289-
_transferEncoding = HTTPC_TE_IDENTITY;
12901452
unsigned long lastDataTime = millis();
12911453

12921454
while(connected()) {
@@ -1514,6 +1676,7 @@ bool HTTPClient::readChunkHeader(bool blocking)
15141676
_chunkLen = (uint32_t) strtol((const char *) _chunkHeader.c_str(), NULL,
15151677
16);
15161678

1679+
_chunkOffset = 0;
15171680
return true;
15181681
}
15191682

libraries/ESP8266HTTPClient/src/ESP8266HTTPClient.h

+9-1
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ typedef std::unique_ptr<TransportTraits> TransportTraitsPtr;
137137

138138
class StreamString;
139139

140-
class HTTPClient
140+
class HTTPClient : public Stream
141141
{
142142
public:
143143
HTTPClient();
@@ -210,6 +210,12 @@ class HTTPClient
210210
const String& getString(void);
211211
static String errorToString(int error);
212212

213+
size_t write(uint8_t b);
214+
size_t write(const uint8_t *buffer, size_t size);
215+
int available();
216+
int read();
217+
int peek();
218+
213219
protected:
214220
struct RequestArgument {
215221
String key;
@@ -222,6 +228,7 @@ class HTTPClient
222228
int returnError(int error);
223229
bool connect(void);
224230
bool sendHeader(const char * type);
231+
bool endRequest(void);
225232
int handleHeaderResponse();
226233
int writeToStreamDataBlock(Stream * stream, int len);
227234

@@ -261,6 +268,7 @@ class HTTPClient
261268
transferEncoding_t _transferEncoding = HTTPC_TE_IDENTITY;
262269
String _chunkHeader;
263270
uint32_t _chunkLen = 0;
271+
uint32_t _chunkOffset = 0;
264272
std::unique_ptr<StreamString> _payload;
265273
};
266274

0 commit comments

Comments
 (0)