mirror of
https://github.com/esp8266/Arduino.git
synced 2025-07-29 05:21:37 +03:00
Stream::send() (#6979)
This commit is contained in:
@ -25,9 +25,22 @@
|
||||
|
||||
#include "ESP8266HTTPClient.h"
|
||||
#include <ESP8266WiFi.h>
|
||||
#include <StreamString.h>
|
||||
#include <StreamDev.h>
|
||||
#include <base64.h>
|
||||
|
||||
static int StreamReportToHttpClientReport (Stream::Report streamSendError)
|
||||
{
|
||||
switch (streamSendError)
|
||||
{
|
||||
case Stream::Report::TimedOut: return HTTPC_ERROR_READ_TIMEOUT;
|
||||
case Stream::Report::ReadError: return HTTPC_ERROR_NO_STREAM;
|
||||
case Stream::Report::WriteError: return HTTPC_ERROR_STREAM_WRITE;
|
||||
case Stream::Report::ShortOperation: return HTTPC_ERROR_STREAM_WRITE;
|
||||
case Stream::Report::Success: return 0;
|
||||
}
|
||||
return 0; // never reached, keep gcc quiet
|
||||
}
|
||||
|
||||
/**
|
||||
* constructor
|
||||
*/
|
||||
@ -429,24 +442,9 @@ int HTTPClient::sendRequest(const char * type, const uint8_t * payload, size_t s
|
||||
return returnError(HTTPC_ERROR_SEND_HEADER_FAILED);
|
||||
}
|
||||
|
||||
// send Payload if needed
|
||||
if (payload && size > 0) {
|
||||
size_t bytesWritten = 0;
|
||||
const uint8_t *p = payload;
|
||||
size_t originalSize = size;
|
||||
while (bytesWritten < originalSize) {
|
||||
int written;
|
||||
int towrite = std::min((int)size, (int)HTTP_TCP_BUFFER_SIZE);
|
||||
written = _client->write(p + bytesWritten, towrite);
|
||||
if (written < 0) {
|
||||
return returnError(HTTPC_ERROR_SEND_PAYLOAD_FAILED);
|
||||
} else if (written == 0) {
|
||||
return returnError(HTTPC_ERROR_CONNECTION_LOST);
|
||||
}
|
||||
bytesWritten += written;
|
||||
size -= written;
|
||||
}
|
||||
}
|
||||
// transfer all of it, with send-timeout
|
||||
if (size && StreamConstPtr(payload, size).sendAll(_client) != size)
|
||||
return returnError(HTTPC_ERROR_SEND_PAYLOAD_FAILED);
|
||||
|
||||
// handle Server Response (Header)
|
||||
code = handleHeaderResponse();
|
||||
@ -545,111 +543,12 @@ int HTTPClient::sendRequest(const char * type, Stream * stream, size_t size)
|
||||
return returnError(HTTPC_ERROR_SEND_HEADER_FAILED);
|
||||
}
|
||||
|
||||
int buff_size = HTTP_TCP_BUFFER_SIZE;
|
||||
|
||||
int len = size;
|
||||
int bytesWritten = 0;
|
||||
|
||||
if(len == 0) {
|
||||
len = -1;
|
||||
}
|
||||
|
||||
// if possible create smaller buffer then HTTP_TCP_BUFFER_SIZE
|
||||
if((len > 0) && (len < HTTP_TCP_BUFFER_SIZE)) {
|
||||
buff_size = len;
|
||||
}
|
||||
|
||||
// create buffer for read
|
||||
uint8_t * buff = (uint8_t *) malloc(buff_size);
|
||||
|
||||
if(buff) {
|
||||
// read all data from stream and send it to server
|
||||
while(connected() && (stream->available() > 0) && (len > 0 || len == -1)) {
|
||||
|
||||
// get available data size
|
||||
int sizeAvailable = stream->available();
|
||||
|
||||
if(sizeAvailable) {
|
||||
|
||||
int readBytes = sizeAvailable;
|
||||
|
||||
// read only the asked bytes
|
||||
if(len > 0 && readBytes > len) {
|
||||
readBytes = len;
|
||||
}
|
||||
|
||||
// not read more the buffer can handle
|
||||
if(readBytes > buff_size) {
|
||||
readBytes = buff_size;
|
||||
}
|
||||
|
||||
// read data
|
||||
int bytesRead = stream->readBytes(buff, readBytes);
|
||||
|
||||
// write it to Stream
|
||||
int bytesWrite = _client->write((const uint8_t *) buff, bytesRead);
|
||||
bytesWritten += bytesWrite;
|
||||
|
||||
// are all Bytes a writen to stream ?
|
||||
if(bytesWrite != bytesRead) {
|
||||
DEBUG_HTTPCLIENT("[HTTP-Client][sendRequest] short write, asked for %d but got %d retry...\n", bytesRead, bytesWrite);
|
||||
|
||||
// check for write error
|
||||
if(_client->getWriteError()) {
|
||||
DEBUG_HTTPCLIENT("[HTTP-Client][sendRequest] stream write error %d\n", _client->getWriteError());
|
||||
|
||||
//reset write error for retry
|
||||
_client->clearWriteError();
|
||||
}
|
||||
|
||||
// some time for the stream
|
||||
delay(1);
|
||||
|
||||
int leftBytes = (readBytes - bytesWrite);
|
||||
|
||||
// retry to send the missed bytes
|
||||
bytesWrite = _client->write((const uint8_t *) (buff + bytesWrite), leftBytes);
|
||||
bytesWritten += bytesWrite;
|
||||
|
||||
if(bytesWrite != leftBytes) {
|
||||
// failed again
|
||||
DEBUG_HTTPCLIENT("[HTTP-Client][sendRequest] short write, asked for %d but got %d failed.\n", leftBytes, bytesWrite);
|
||||
free(buff);
|
||||
return returnError(HTTPC_ERROR_SEND_PAYLOAD_FAILED);
|
||||
}
|
||||
}
|
||||
|
||||
// check for write error
|
||||
if(_client->getWriteError()) {
|
||||
DEBUG_HTTPCLIENT("[HTTP-Client][sendRequest] stream write error %d\n", _client->getWriteError());
|
||||
free(buff);
|
||||
return returnError(HTTPC_ERROR_SEND_PAYLOAD_FAILED);
|
||||
}
|
||||
|
||||
// count bytes to read left
|
||||
if(len > 0) {
|
||||
len -= readBytes;
|
||||
}
|
||||
|
||||
delay(0);
|
||||
} else {
|
||||
delay(1);
|
||||
}
|
||||
}
|
||||
|
||||
free(buff);
|
||||
|
||||
if(size && (int) size != bytesWritten) {
|
||||
DEBUG_HTTPCLIENT("[HTTP-Client][sendRequest] Stream payload bytesWritten %d and size %zd mismatch!.\n", bytesWritten, size);
|
||||
DEBUG_HTTPCLIENT("[HTTP-Client][sendRequest] ERROR SEND PAYLOAD FAILED!");
|
||||
return returnError(HTTPC_ERROR_SEND_PAYLOAD_FAILED);
|
||||
} else {
|
||||
DEBUG_HTTPCLIENT("[HTTP-Client][sendRequest] Stream payload written: %d\n", bytesWritten);
|
||||
}
|
||||
|
||||
} else {
|
||||
DEBUG_HTTPCLIENT("[HTTP-Client][sendRequest] not enough ram! need %d\n", HTTP_TCP_BUFFER_SIZE);
|
||||
return returnError(HTTPC_ERROR_TOO_LESS_RAM);
|
||||
// transfer all of it, with timeout
|
||||
size_t transferred = stream->sendSize(_client, size);
|
||||
if (transferred != size)
|
||||
{
|
||||
DEBUG_HTTPCLIENT("[HTTP-Client][sendRequest] short write, asked for %d but got %d failed.\n", size, transferred);
|
||||
return returnError(HTTPC_ERROR_SEND_PAYLOAD_FAILED);
|
||||
}
|
||||
|
||||
// handle Server Response (Header)
|
||||
@ -725,13 +624,13 @@ int HTTPClient::writeToStream(Stream * stream)
|
||||
int ret = 0;
|
||||
|
||||
if(_transferEncoding == HTTPC_TE_IDENTITY) {
|
||||
if(len > 0 || len == -1) {
|
||||
ret = writeToStreamDataBlock(stream, len);
|
||||
// len < 0: transfer all of it, with timeout
|
||||
// len >= 0: max:len, with timeout
|
||||
ret = _client->sendSize(stream, len);
|
||||
|
||||
// have we an error?
|
||||
if(ret < 0) {
|
||||
return returnError(ret);
|
||||
}
|
||||
// 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;
|
||||
@ -754,11 +653,11 @@ int HTTPClient::writeToStream(Stream * stream)
|
||||
|
||||
// data left?
|
||||
if(len > 0) {
|
||||
int r = writeToStreamDataBlock(stream, len);
|
||||
if(r < 0) {
|
||||
// error in writeToStreamDataBlock
|
||||
return returnError(r);
|
||||
}
|
||||
// read len bytes with timeout
|
||||
int r = _client->sendSize(stream, len);
|
||||
if (_client->getLastSendReport() != Stream::Report::Success)
|
||||
// not all data transferred
|
||||
return returnError(StreamReportToHttpClientReport(_client->getLastSendReport()));
|
||||
ret += r;
|
||||
} else {
|
||||
|
||||
@ -948,9 +847,7 @@ bool HTTPClient::connect(void)
|
||||
{
|
||||
if(_reuse && _canReuse && connected()) {
|
||||
DEBUG_HTTPCLIENT("[HTTP-Client] connect: already connected, reusing connection\n");
|
||||
while(_client->available() > 0) {
|
||||
_client->read();
|
||||
}
|
||||
_client->sendAvailable(devnull); // clear _client's output (all of it, no timeout)
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -1032,7 +929,8 @@ bool HTTPClient::sendHeader(const char * type)
|
||||
|
||||
DEBUG_HTTPCLIENT("[HTTP-Client] sending request header\n-----\n%s-----\n", header.c_str());
|
||||
|
||||
return (_client->write((const uint8_t *) header.c_str(), header.length()) == header.length());
|
||||
// transfer all of it, with timeout
|
||||
return StreamConstPtr(header).sendAll(_client) == header.length();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1150,116 +1048,6 @@ int HTTPClient::handleHeaderResponse()
|
||||
return HTTPC_ERROR_CONNECTION_LOST;
|
||||
}
|
||||
|
||||
/**
|
||||
* write one Data Block to Stream
|
||||
* @param stream Stream *
|
||||
* @param size int
|
||||
* @return < 0 = error >= 0 = size written
|
||||
*/
|
||||
int HTTPClient::writeToStreamDataBlock(Stream * stream, int size)
|
||||
{
|
||||
int buff_size = HTTP_TCP_BUFFER_SIZE;
|
||||
int len = size; // left size to read
|
||||
int bytesWritten = 0;
|
||||
|
||||
// if possible create smaller buffer then HTTP_TCP_BUFFER_SIZE
|
||||
if((len > 0) && (len < HTTP_TCP_BUFFER_SIZE)) {
|
||||
buff_size = len;
|
||||
}
|
||||
|
||||
// create buffer for read
|
||||
uint8_t * buff = (uint8_t *) malloc(buff_size);
|
||||
|
||||
if(!buff) {
|
||||
DEBUG_HTTPCLIENT("[HTTP-Client][writeToStreamDataBlock] not enough ram! need %d\n", HTTP_TCP_BUFFER_SIZE);
|
||||
return HTTPC_ERROR_TOO_LESS_RAM;
|
||||
}
|
||||
|
||||
// read all data from server
|
||||
while(connected() && (len > 0 || len == -1))
|
||||
{
|
||||
int readBytes = len;
|
||||
|
||||
// not read more the buffer can handle
|
||||
if(readBytes > buff_size) {
|
||||
readBytes = buff_size;
|
||||
}
|
||||
|
||||
// len == -1 or len > what is available, read only what is available
|
||||
int av = _client->available();
|
||||
if (readBytes < 0 || readBytes > av) {
|
||||
readBytes = av;
|
||||
}
|
||||
|
||||
// read data
|
||||
int bytesRead = _client->readBytes(buff, readBytes);
|
||||
if (!bytesRead)
|
||||
{
|
||||
DEBUG_HTTPCLIENT("[HTTP-Client][writeToStreamDataBlock] input stream timeout\n");
|
||||
free(buff);
|
||||
return HTTPC_ERROR_READ_TIMEOUT;
|
||||
}
|
||||
|
||||
// write it to Stream
|
||||
int bytesWrite = stream->write(buff, bytesRead);
|
||||
bytesWritten += bytesWrite;
|
||||
|
||||
// are all Bytes a writen to stream ?
|
||||
if(bytesWrite != bytesRead) {
|
||||
DEBUG_HTTPCLIENT("[HTTP-Client][writeToStream] short write asked for %d but got %d retry...\n", bytesRead, bytesWrite);
|
||||
|
||||
// check for write error
|
||||
if(stream->getWriteError()) {
|
||||
DEBUG_HTTPCLIENT("[HTTP-Client][writeToStreamDataBlock] stream write error %d\n", stream->getWriteError());
|
||||
|
||||
//reset write error for retry
|
||||
stream->clearWriteError();
|
||||
}
|
||||
|
||||
// some time for the stream
|
||||
delay(1);
|
||||
|
||||
int leftBytes = (bytesRead - bytesWrite);
|
||||
|
||||
// retry to send the missed bytes
|
||||
bytesWrite = stream->write((buff + bytesWrite), leftBytes);
|
||||
bytesWritten += bytesWrite;
|
||||
|
||||
if(bytesWrite != leftBytes) {
|
||||
// failed again
|
||||
DEBUG_HTTPCLIENT("[HTTP-Client][writeToStream] short write asked for %d but got %d failed.\n", leftBytes, bytesWrite);
|
||||
free(buff);
|
||||
return HTTPC_ERROR_STREAM_WRITE;
|
||||
}
|
||||
}
|
||||
|
||||
// check for write error
|
||||
if(stream->getWriteError()) {
|
||||
DEBUG_HTTPCLIENT("[HTTP-Client][writeToStreamDataBlock] stream write error %d\n", stream->getWriteError());
|
||||
free(buff);
|
||||
return HTTPC_ERROR_STREAM_WRITE;
|
||||
}
|
||||
|
||||
// count bytes to read left
|
||||
if(len > 0) {
|
||||
len -= bytesRead;
|
||||
}
|
||||
|
||||
delay(0);
|
||||
}
|
||||
|
||||
free(buff);
|
||||
|
||||
DEBUG_HTTPCLIENT("[HTTP-Client][writeToStreamDataBlock] end of chunk or data (transferred: %d).\n", bytesWritten);
|
||||
|
||||
if((size > 0) && (size != bytesWritten)) {
|
||||
DEBUG_HTTPCLIENT("[HTTP-Client][writeToStreamDataBlock] transferred size %d and request size %d mismatch!.\n", bytesWritten, size);
|
||||
return HTTPC_ERROR_STREAM_WRITE;
|
||||
}
|
||||
|
||||
return bytesWritten;
|
||||
}
|
||||
|
||||
/**
|
||||
* called to handle error return, may disconnect the connection if still exists
|
||||
* @param error
|
||||
|
@ -28,7 +28,7 @@
|
||||
|
||||
#include <memory>
|
||||
#include <Arduino.h>
|
||||
|
||||
#include <StreamString.h>
|
||||
#include <WiFiClient.h>
|
||||
|
||||
#ifdef DEBUG_ESP_HTTP_CLIENT
|
||||
@ -148,8 +148,6 @@ typedef enum {
|
||||
class TransportTraits;
|
||||
typedef std::unique_ptr<TransportTraits> TransportTraitsPtr;
|
||||
|
||||
class StreamString;
|
||||
|
||||
class HTTPClient
|
||||
{
|
||||
public:
|
||||
|
@ -115,9 +115,9 @@ void setup(void) {
|
||||
// swallow the exact amount matching the full request+content,
|
||||
// hence the tcp connection cannot be handled anymore by the
|
||||
// webserver.
|
||||
#ifdef STREAMTO_API
|
||||
#ifdef STREAMSEND_API
|
||||
// we are lucky
|
||||
client->toWithTimeout(Serial, 500);
|
||||
client->sendAll(Serial, 500);
|
||||
#else
|
||||
auto last = millis();
|
||||
while ((millis() - last) < 500) {
|
||||
|
@ -28,6 +28,7 @@
|
||||
#include "FS.h"
|
||||
#include "base64.h"
|
||||
#include "detail/RequestHandlersImpl.h"
|
||||
#include <StreamDev.h>
|
||||
|
||||
static const char AUTHORIZATION_HEADER[] PROGMEM = "Authorization";
|
||||
static const char qop_auth[] PROGMEM = "qop=auth";
|
||||
@ -440,72 +441,69 @@ void ESP8266WebServerTemplate<ServerType>::_prepareHeader(String& response, int
|
||||
_responseHeaders = "";
|
||||
}
|
||||
|
||||
template <typename ServerType>
|
||||
void ESP8266WebServerTemplate<ServerType>::send(int code, const char* content_type, const String& content) {
|
||||
String header;
|
||||
// Can we asume the following?
|
||||
//if(code == 200 && content.length() == 0 && _contentLength == CONTENT_LENGTH_NOT_SET)
|
||||
// _contentLength = CONTENT_LENGTH_UNKNOWN;
|
||||
_prepareHeader(header, code, content_type, content.length());
|
||||
_currentClient.write((const uint8_t *)header.c_str(), header.length());
|
||||
if(content.length())
|
||||
sendContent(content);
|
||||
}
|
||||
|
||||
template <typename ServerType>
|
||||
void ESP8266WebServerTemplate<ServerType>::send_P(int code, PGM_P content_type, PGM_P content) {
|
||||
size_t contentLength = 0;
|
||||
|
||||
if (content != NULL) {
|
||||
contentLength = strlen_P(content);
|
||||
}
|
||||
|
||||
String header;
|
||||
char type[64];
|
||||
memccpy_P((void*)type, (PGM_VOID_P)content_type, 0, sizeof(type));
|
||||
_prepareHeader(header, code, (const char* )type, contentLength);
|
||||
_currentClient.write((const uint8_t *)header.c_str(), header.length());
|
||||
if (contentLength) {
|
||||
sendContent_P(content);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename ServerType>
|
||||
void ESP8266WebServerTemplate<ServerType>::send_P(int code, PGM_P content_type, PGM_P content, size_t contentLength) {
|
||||
String header;
|
||||
char type[64];
|
||||
memccpy_P((void*)type, (PGM_VOID_P)content_type, 0, sizeof(type));
|
||||
_prepareHeader(header, code, (const char* )type, contentLength);
|
||||
_currentClient.write((const uint8_t *)header.c_str(), header.length());
|
||||
if (contentLength) {
|
||||
sendContent_P(content, contentLength);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename ServerType>
|
||||
void ESP8266WebServerTemplate<ServerType>::send(int code, char* content_type, const String& content) {
|
||||
send(code, (const char*)content_type, content);
|
||||
return send(code, (const char*)content_type, content);
|
||||
}
|
||||
|
||||
template <typename ServerType>
|
||||
void ESP8266WebServerTemplate<ServerType>::send(int code, const char* content_type, const String& content) {
|
||||
return send(code, content_type, content.c_str(), content.length());
|
||||
}
|
||||
|
||||
template <typename ServerType>
|
||||
void ESP8266WebServerTemplate<ServerType>::send(int code, const String& content_type, const String& content) {
|
||||
send(code, (const char*)content_type.c_str(), content);
|
||||
return send(code, (const char*)content_type.c_str(), content);
|
||||
}
|
||||
|
||||
template <typename ServerType>
|
||||
void ESP8266WebServerTemplate<ServerType>::sendContent(const String& content) {
|
||||
if (_currentMethod == HTTP_HEAD) return;
|
||||
const char * footer = "\r\n";
|
||||
size_t len = content.length();
|
||||
StreamConstPtr ref(content.c_str(), content.length());
|
||||
sendContent(&ref);
|
||||
}
|
||||
|
||||
template <typename ServerType>
|
||||
void ESP8266WebServerTemplate<ServerType>::send(int code, const char* content_type, Stream* stream, size_t content_length /*= 0*/) {
|
||||
String header;
|
||||
if (content_length == 0)
|
||||
content_length = std::max((ssize_t)0, stream->streamRemaining());
|
||||
_prepareHeader(header, code, content_type, content_length);
|
||||
size_t sent = StreamConstPtr(header).sendAll(&_currentClient);
|
||||
if (sent != header.length())
|
||||
DBGWS("HTTPServer: error: sent %zd on %u bytes\n", sent, header.length());
|
||||
if (content_length)
|
||||
return sendContent(stream, content_length);
|
||||
}
|
||||
|
||||
template <typename ServerType>
|
||||
void ESP8266WebServerTemplate<ServerType>::send_P(int code, PGM_P content_type, PGM_P content) {
|
||||
StreamConstPtr ref(content, strlen_P(content));
|
||||
return send(code, String(content_type).c_str(), &ref);
|
||||
}
|
||||
|
||||
template <typename ServerType>
|
||||
void ESP8266WebServerTemplate<ServerType>::send_P(int code, PGM_P content_type, PGM_P content, size_t contentLength) {
|
||||
StreamConstPtr ref(content, contentLength);
|
||||
return send(code, String(content_type).c_str(), &ref);
|
||||
}
|
||||
|
||||
template <typename ServerType>
|
||||
void ESP8266WebServerTemplate<ServerType>::sendContent(Stream* content, ssize_t content_length /* = 0*/) {
|
||||
if (_currentMethod == HTTP_HEAD)
|
||||
return;
|
||||
if (content_length <= 0)
|
||||
content_length = std::max((ssize_t)0, content->streamRemaining());
|
||||
if(_chunked) {
|
||||
char chunkSize[11];
|
||||
sprintf(chunkSize, "%zx\r\n", len);
|
||||
_currentClient.write((const uint8_t *)chunkSize, strlen(chunkSize));
|
||||
_currentClient.printf("%zx\r\n", content_length);
|
||||
}
|
||||
_currentClient.write((const uint8_t *)content.c_str(), len);
|
||||
if(_chunked){
|
||||
_currentClient.write((const uint8_t *)footer, 2);
|
||||
if (len == 0) {
|
||||
ssize_t sent = content->sendSize(&_currentClient, content_length);
|
||||
if (sent != content_length)
|
||||
{
|
||||
DBGWS("HTTPServer: error: short send after timeout (%d<%d)\n", sent, content_length);
|
||||
}
|
||||
if(_chunked) {
|
||||
_currentClient.printf_P(PSTR("\r\n"));
|
||||
if (content_length == 0) {
|
||||
_chunked = false;
|
||||
}
|
||||
}
|
||||
@ -518,19 +516,8 @@ void ESP8266WebServerTemplate<ServerType>::sendContent_P(PGM_P content) {
|
||||
|
||||
template <typename ServerType>
|
||||
void ESP8266WebServerTemplate<ServerType>::sendContent_P(PGM_P content, size_t size) {
|
||||
const char * footer = "\r\n";
|
||||
if(_chunked) {
|
||||
char chunkSize[11];
|
||||
sprintf(chunkSize, "%zx\r\n", size);
|
||||
_currentClient.write((const uint8_t *)chunkSize, strlen(chunkSize));
|
||||
}
|
||||
_currentClient.write_P(content, size);
|
||||
if(_chunked){
|
||||
_currentClient.write((const uint8_t *)footer, 2);
|
||||
if (size == 0) {
|
||||
_chunked = false;
|
||||
}
|
||||
}
|
||||
StreamConstPtr ptr(content, size);
|
||||
return sendContent(&ptr, size);
|
||||
}
|
||||
|
||||
template <typename ServerType>
|
||||
@ -694,7 +681,7 @@ void ESP8266WebServerTemplate<ServerType>::_handleRequest() {
|
||||
}
|
||||
if (!handled) {
|
||||
using namespace mime;
|
||||
send(404, String(FPSTR(mimeTable[html].mimeType)), String(F("Not found: ")) + _currentUri);
|
||||
send(404, FPSTR(mimeTable[html].mimeType), String(F("Not found: ")) + _currentUri);
|
||||
handled = true;
|
||||
}
|
||||
if (handled) {
|
||||
|
@ -149,7 +149,7 @@ public:
|
||||
// code - HTTP response code, can be 200 or 404
|
||||
// content_type - HTTP content type, like "text/plain" or "image/png"
|
||||
// content - actual content body
|
||||
void send(int code, const char* content_type = NULL, const String& content = String(""));
|
||||
void send(int code, const char* content_type = NULL, const String& content = emptyString);
|
||||
void send(int code, char* content_type, const String& content);
|
||||
void send(int code, const String& content_type, const String& content);
|
||||
void send(int code, const char *content_type, const char *content) {
|
||||
@ -164,14 +164,23 @@ public:
|
||||
void send_P(int code, PGM_P content_type, PGM_P content);
|
||||
void send_P(int code, PGM_P content_type, PGM_P content, size_t contentLength);
|
||||
|
||||
void send(int code, const char* content_type, Stream* stream, size_t content_length = 0);
|
||||
void send(int code, const char* content_type, Stream& stream, size_t content_length = 0);
|
||||
|
||||
void setContentLength(const size_t contentLength);
|
||||
void sendHeader(const String& name, const String& value, bool first = false);
|
||||
void sendContent(const String& content);
|
||||
void sendContent(String& content) {
|
||||
sendContent((const String&)content);
|
||||
}
|
||||
void sendContent_P(PGM_P content);
|
||||
void sendContent_P(PGM_P content, size_t size);
|
||||
void sendContent(const char *content) { sendContent_P(content); }
|
||||
void sendContent(const char *content, size_t size) { sendContent_P(content, size); }
|
||||
|
||||
void sendContent(Stream* content, ssize_t content_length = 0);
|
||||
void sendContent(Stream& content, ssize_t content_length = 0) { sendContent(&content, content_length); }
|
||||
|
||||
bool chunkedResponseModeStart_P (int code, PGM_P content_type) {
|
||||
if (_currentVersion == 0)
|
||||
// no chunk mode in HTTP/1.0
|
||||
@ -220,6 +229,30 @@ public:
|
||||
return contentLength;
|
||||
}
|
||||
|
||||
// Implement GET and HEAD requests for stream
|
||||
// Stream body on HTTP_GET but not on HTTP_HEAD requests.
|
||||
template<typename T>
|
||||
size_t stream(T &aStream, const String& contentType, HTTPMethod requestMethod, ssize_t size) {
|
||||
setContentLength(size);
|
||||
send(200, contentType, emptyString);
|
||||
if (requestMethod == HTTP_GET)
|
||||
size = aStream.sendSize(_currentClient, size);
|
||||
return size;
|
||||
}
|
||||
|
||||
// Implement GET and HEAD requests for stream
|
||||
// Stream body on HTTP_GET but not on HTTP_HEAD requests.
|
||||
template<typename T>
|
||||
size_t stream(T& aStream, const String& contentType, HTTPMethod requestMethod = HTTP_GET) {
|
||||
ssize_t size = aStream.size();
|
||||
if (size < 0)
|
||||
{
|
||||
send(500, F("text/html"), F("input stream: undetermined size"));
|
||||
return 0;
|
||||
}
|
||||
return stream(aStream, contentType, requestMethod, size);
|
||||
}
|
||||
|
||||
static String responseCodeToString(const int code);
|
||||
|
||||
void addHook (HookFunction hook) {
|
||||
|
@ -37,22 +37,8 @@ namespace esp8266webserver {
|
||||
template <typename ServerType>
|
||||
static bool readBytesWithTimeout(typename ServerType::ClientType& client, size_t maxLength, String& data, int timeout_ms)
|
||||
{
|
||||
if (!data.reserve(maxLength + 1))
|
||||
return false;
|
||||
data[0] = 0; // data.clear()??
|
||||
while (data.length() < maxLength) {
|
||||
int tries = timeout_ms;
|
||||
size_t avail;
|
||||
while (!(avail = client.available()) && tries--)
|
||||
delay(1);
|
||||
if (!avail)
|
||||
break;
|
||||
if (data.length() + avail > maxLength)
|
||||
avail = maxLength - data.length();
|
||||
while (avail--)
|
||||
data += (char)client.read();
|
||||
}
|
||||
return data.length() == maxLength;
|
||||
S2Stream dataStream(data);
|
||||
return client.sendSize(dataStream, maxLength, timeout_ms) == maxLength;
|
||||
}
|
||||
|
||||
template <typename ServerType>
|
||||
|
141
libraries/ESP8266WiFi/examples/WiFiEcho/WiFiEcho.ino
Normal file
141
libraries/ESP8266WiFi/examples/WiFiEcho/WiFiEcho.ino
Normal file
@ -0,0 +1,141 @@
|
||||
/*
|
||||
WiFiEcho - Echo server
|
||||
|
||||
released to public domain
|
||||
*/
|
||||
|
||||
#include <ESP8266WiFi.h>
|
||||
#include <ESP8266mDNS.h>
|
||||
#include <PolledTimeout.h>
|
||||
#include <algorithm> // std::min
|
||||
|
||||
#ifndef STASSID
|
||||
#define STASSID "your-ssid"
|
||||
#define STAPSK "your-password"
|
||||
#endif
|
||||
|
||||
constexpr int port = 23;
|
||||
|
||||
WiFiServer server(port);
|
||||
WiFiClient client;
|
||||
|
||||
constexpr size_t sizes [] = { 0, 512, 384, 256, 128, 64, 16, 8, 4 };
|
||||
constexpr uint32_t breathMs = 200;
|
||||
esp8266::polledTimeout::oneShotFastMs enoughMs(breathMs);
|
||||
esp8266::polledTimeout::periodicFastMs test(2000);
|
||||
int t = 1; // test (1, 2 or 3, see below)
|
||||
int s = 0; // sizes[] index
|
||||
|
||||
void setup() {
|
||||
|
||||
Serial.begin(115200);
|
||||
Serial.println(ESP.getFullVersion());
|
||||
|
||||
WiFi.mode(WIFI_STA);
|
||||
WiFi.begin(STASSID, STAPSK);
|
||||
Serial.print("\nConnecting to ");
|
||||
Serial.println(STASSID);
|
||||
while (WiFi.status() != WL_CONNECTED) {
|
||||
Serial.print('.');
|
||||
delay(500);
|
||||
}
|
||||
Serial.println();
|
||||
Serial.print("connected, address=");
|
||||
Serial.println(WiFi.localIP());
|
||||
|
||||
server.begin();
|
||||
|
||||
MDNS.begin("echo23");
|
||||
|
||||
Serial.printf("Ready!\n"
|
||||
"- Use 'telnet/nc echo23.local %d' to try echo\n\n"
|
||||
"- Use 'python3 echo-client.py' bandwidth meter to compare transfer APIs\n\n"
|
||||
" and try typing 1, 1, 1, 2, 2, 2, 3, 3, 3 on console during transfers\n\n",
|
||||
port);
|
||||
}
|
||||
|
||||
|
||||
void loop() {
|
||||
|
||||
MDNS.update();
|
||||
|
||||
static uint32_t tot = 0;
|
||||
static uint32_t cnt = 0;
|
||||
if (test && cnt) {
|
||||
Serial.printf("measured-block-size=%u min-free-stack=%u", tot / cnt, ESP.getFreeContStack());
|
||||
if (t == 2 && sizes[s]) {
|
||||
Serial.printf(" (blocks: at most %d bytes)", sizes[s]);
|
||||
}
|
||||
if (t == 3 && sizes[s]) {
|
||||
Serial.printf(" (blocks: exactly %d bytes)", sizes[s]);
|
||||
}
|
||||
if (t == 3 && !sizes[s]) {
|
||||
Serial.printf(" (blocks: any size)");
|
||||
}
|
||||
Serial.printf("\n");
|
||||
}
|
||||
|
||||
//check if there are any new clients
|
||||
if (server.hasClient()) {
|
||||
client = server.available();
|
||||
Serial.println("New client");
|
||||
}
|
||||
|
||||
if (Serial.available()) {
|
||||
s = (s + 1) % (sizeof(sizes) / sizeof(sizes[0]));
|
||||
switch (Serial.read()) {
|
||||
case '1': if (t != 1) s = 0; t = 1; Serial.println("byte-by-byte (watch then press 2 or 3)"); break;
|
||||
case '2': if (t != 2) s = 1; t = 2; Serial.printf("through buffer (watch then press 2 again, or 1 or 3)\n"); break;
|
||||
case '3': if (t != 3) s = 0; t = 3; Serial.printf("direct access (watch then press 3 again, or 1 or 2)\n"); break;
|
||||
}
|
||||
tot = cnt = 0;
|
||||
ESP.resetFreeContStack();
|
||||
}
|
||||
|
||||
enoughMs.reset(breathMs);
|
||||
|
||||
if (t == 1) {
|
||||
// byte by byte
|
||||
while (client.available() && client.availableForWrite() && !enoughMs) {
|
||||
// working char by char is not efficient
|
||||
client.write(client.read());
|
||||
cnt++;
|
||||
tot += 1;
|
||||
}
|
||||
}
|
||||
|
||||
else if (t == 2) {
|
||||
// block by block through a local buffer (2 copies)
|
||||
while (client.available() && client.availableForWrite() && !enoughMs) {
|
||||
size_t maxTo = std::min(client.available(), client.availableForWrite());
|
||||
maxTo = std::min(maxTo, sizes[s]);
|
||||
uint8_t buf[maxTo];
|
||||
size_t tcp_got = client.read(buf, maxTo);
|
||||
size_t tcp_sent = client.write(buf, tcp_got);
|
||||
if (tcp_sent != maxTo) {
|
||||
Serial.printf("len mismatch: available:%zd tcp-read:%zd serial-write:%zd\n", maxTo, tcp_got, tcp_sent);
|
||||
}
|
||||
tot += tcp_sent;
|
||||
cnt++;
|
||||
}
|
||||
}
|
||||
|
||||
else if (t == 3) {
|
||||
// stream to print, possibly with only one copy
|
||||
if (sizes[s]) {
|
||||
tot += client.sendSize(&client, sizes[s]);
|
||||
} else {
|
||||
tot += client.sendAll(&client);
|
||||
}
|
||||
cnt++;
|
||||
|
||||
switch (client.getLastSendReport()) {
|
||||
case Stream::Report::Success: break;
|
||||
case Stream::Report::TimedOut: Serial.println("Stream::send: timeout"); break;
|
||||
case Stream::Report::ReadError: Serial.println("Stream::send: read error"); break;
|
||||
case Stream::Report::WriteError: Serial.println("Stream::send: write error"); break;
|
||||
case Stream::Report::ShortOperation: Serial.println("Stream::send: short transfer"); break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
49
libraries/ESP8266WiFi/examples/WiFiEcho/echo-client.py
Executable file
49
libraries/ESP8266WiFi/examples/WiFiEcho/echo-client.py
Executable file
@ -0,0 +1,49 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import os
|
||||
import asyncio
|
||||
|
||||
# 512 bytes
|
||||
message = bytearray(512);
|
||||
bufsize=len(message)
|
||||
print('message len=', bufsize)
|
||||
|
||||
global recv
|
||||
recv = 0
|
||||
|
||||
async def tcp_echo_open (ip, port):
|
||||
return await asyncio.open_connection(ip, port)
|
||||
|
||||
async def tcp_echo_sender(message, writer):
|
||||
print('Writer started')
|
||||
while True:
|
||||
writer.write(message)
|
||||
await writer.drain()
|
||||
|
||||
async def tcp_echo_receiver(message, reader):
|
||||
global recv
|
||||
print('Reader started')
|
||||
while True:
|
||||
data = ''.encode('utf8')
|
||||
while len(data) < bufsize:
|
||||
data += await reader.read(bufsize - len(data))
|
||||
recv += len(data);
|
||||
if data != message:
|
||||
print('error')
|
||||
|
||||
async def tcp_stat():
|
||||
global recv
|
||||
dur = 0
|
||||
loopsec = 2
|
||||
while True:
|
||||
last = recv
|
||||
await asyncio.sleep(loopsec) # drifting
|
||||
dur += loopsec
|
||||
print('BW=', (recv - last) * 2 * 8 / 1024 / loopsec, 'Kibits/s avg=', recv * 2 * 8 / 1024 / dur)
|
||||
|
||||
loop = asyncio.get_event_loop()
|
||||
reader, writer = loop.run_until_complete(tcp_echo_open('echo23.local', 23))
|
||||
loop.create_task(tcp_echo_receiver(message, reader))
|
||||
loop.create_task(tcp_echo_sender(message, writer))
|
||||
loop.create_task(tcp_stat())
|
||||
loop.run_forever()
|
@ -125,7 +125,7 @@ int CertStore::initCertStore(fs::FS &fs, const char *indexFileName, const char *
|
||||
uint8_t fileHeader[60];
|
||||
// 0..15 = filename in ASCII
|
||||
// 48...57 = length in decimal ASCII
|
||||
uint32_t length;
|
||||
int32_t length;
|
||||
if (data.read(fileHeader, sizeof(fileHeader)) != sizeof(fileHeader)) {
|
||||
break;
|
||||
}
|
||||
@ -201,7 +201,7 @@ const br_x509_trust_anchor *CertStore::findHashedTA(void *ctx, void *hashed_dn,
|
||||
free(der);
|
||||
return nullptr;
|
||||
}
|
||||
if (data.read((uint8_t *)der, ci.length) != ci.length) {
|
||||
if (data.read(der, ci.length) != (int)ci.length) {
|
||||
free(der);
|
||||
return nullptr;
|
||||
}
|
||||
|
@ -244,7 +244,7 @@ size_t WiFiClient::write_P(PGM_P buf, size_t size)
|
||||
int WiFiClient::available()
|
||||
{
|
||||
if (!_client)
|
||||
return false;
|
||||
return 0;
|
||||
|
||||
int result = _client->getSize();
|
||||
|
||||
@ -262,10 +262,14 @@ int WiFiClient::read()
|
||||
return _client->read();
|
||||
}
|
||||
|
||||
|
||||
int WiFiClient::read(uint8_t* buf, size_t size)
|
||||
{
|
||||
return (int) _client->read(reinterpret_cast<char*>(buf), size);
|
||||
return (int)_client->read((char*)buf, size);
|
||||
}
|
||||
|
||||
int WiFiClient::read(char* buf, size_t size)
|
||||
{
|
||||
return (int)_client->read(buf, size);
|
||||
}
|
||||
|
||||
int WiFiClient::peek()
|
||||
@ -412,3 +416,28 @@ uint8_t WiFiClient::getKeepAliveCount () const
|
||||
{
|
||||
return _client->getKeepAliveCount();
|
||||
}
|
||||
|
||||
bool WiFiClient::hasPeekBufferAPI () const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// return a pointer to available data buffer (size = peekAvailable())
|
||||
// semantic forbids any kind of read() before calling peekConsume()
|
||||
const char* WiFiClient::peekBuffer ()
|
||||
{
|
||||
return _client? _client->peekBuffer(): nullptr;
|
||||
}
|
||||
|
||||
// return number of byte accessible by peekBuffer()
|
||||
size_t WiFiClient::peekAvailable ()
|
||||
{
|
||||
return _client? _client->peekAvailable(): 0;
|
||||
}
|
||||
|
||||
// consume bytes after use (see peekBuffer)
|
||||
void WiFiClient::peekConsume (size_t consume)
|
||||
{
|
||||
if (_client)
|
||||
_client->peekConsume(consume);
|
||||
}
|
||||
|
@ -66,7 +66,9 @@ public:
|
||||
|
||||
virtual int available() override;
|
||||
virtual int read() override;
|
||||
virtual int read(uint8_t *buf, size_t size) override;
|
||||
virtual int read(uint8_t* buf, size_t size) override;
|
||||
int read(char* buf, size_t size);
|
||||
|
||||
virtual int peek() override;
|
||||
virtual size_t peekBytes(uint8_t *buffer, size_t length);
|
||||
size_t peekBytes(char *buffer, size_t length) {
|
||||
@ -120,6 +122,22 @@ public:
|
||||
bool getSync() const;
|
||||
void setSync(bool sync);
|
||||
|
||||
// peek buffer API is present
|
||||
virtual bool hasPeekBufferAPI () const override;
|
||||
|
||||
// return number of byte accessible by peekBuffer()
|
||||
virtual size_t peekAvailable () override;
|
||||
|
||||
// return a pointer to available data buffer (size = peekAvailable())
|
||||
// semantic forbids any kind of read() before calling peekConsume()
|
||||
virtual const char* peekBuffer () override;
|
||||
|
||||
// consume bytes after use (see peekBuffer)
|
||||
virtual void peekConsume (size_t consume) override;
|
||||
|
||||
virtual bool outputCanTimeout () override { return connected(); }
|
||||
virtual bool inputCanTimeout () override { return connected(); }
|
||||
|
||||
protected:
|
||||
|
||||
static int8_t _s_connected(void* arg, void* tpcb, int8_t err);
|
||||
|
@ -362,6 +362,22 @@ int WiFiClientSecureCtx::read(uint8_t *buf, size_t size) {
|
||||
return 0; // If we're connected, no error but no read.
|
||||
}
|
||||
|
||||
// return a pointer to available data buffer (size = peekAvailable())
|
||||
// semantic forbids any kind of read() before calling peekConsume()
|
||||
const char* WiFiClientSecureCtx::peekBuffer ()
|
||||
{
|
||||
return (const char*)_recvapp_buf;
|
||||
}
|
||||
|
||||
// consume bytes after use (see peekBuffer)
|
||||
void WiFiClientSecureCtx::peekConsume (size_t consume)
|
||||
{
|
||||
// according to WiFiClientSecureCtx::read:
|
||||
br_ssl_engine_recvapp_ack(_eng, consume);
|
||||
_recvapp_buf = nullptr;
|
||||
_recvapp_len = 0;
|
||||
}
|
||||
|
||||
int WiFiClientSecureCtx::read() {
|
||||
uint8_t c;
|
||||
if (1 == read(&c, 1)) {
|
||||
|
@ -48,6 +48,7 @@ class WiFiClientSecureCtx : public WiFiClient {
|
||||
size_t write_P(PGM_P buf, size_t size) override;
|
||||
size_t write(Stream& stream); // Note this is not virtual
|
||||
int read(uint8_t *buf, size_t size) override;
|
||||
int read(char *buf, size_t size) { return read((uint8_t*)buf, size); }
|
||||
int available() override;
|
||||
int read() override;
|
||||
int peek() override;
|
||||
@ -120,6 +121,19 @@ class WiFiClientSecureCtx : public WiFiClient {
|
||||
bool setCiphers(const std::vector<uint16_t>& list);
|
||||
bool setCiphersLessSecure(); // Only use the limited set of RSA ciphers without EC
|
||||
|
||||
// peek buffer API is present
|
||||
virtual bool hasPeekBufferAPI () const override { return true; }
|
||||
|
||||
// return number of byte accessible by peekBuffer()
|
||||
virtual size_t peekAvailable () override { return WiFiClientSecureCtx::available(); }
|
||||
|
||||
// return a pointer to available data buffer (size = peekAvailable())
|
||||
// semantic forbids any kind of read() before calling peekConsume()
|
||||
virtual const char* peekBuffer () override;
|
||||
|
||||
// consume bytes after use (see peekBuffer)
|
||||
virtual void peekConsume (size_t consume) override;
|
||||
|
||||
protected:
|
||||
bool _connectSSL(const char *hostName); // Do initial SSL handshake
|
||||
|
||||
@ -287,6 +301,19 @@ class WiFiClientSecure : public WiFiClient {
|
||||
static bool probeMaxFragmentLength(const char *hostname, uint16_t port, uint16_t len);
|
||||
static bool probeMaxFragmentLength(const String& host, uint16_t port, uint16_t len);
|
||||
|
||||
// peek buffer API is present
|
||||
virtual bool hasPeekBufferAPI () const override { return true; }
|
||||
|
||||
// return number of byte accessible by peekBuffer()
|
||||
virtual size_t peekAvailable () override { return _ctx->available(); }
|
||||
|
||||
// return a pointer to available data buffer (size = peekAvailable())
|
||||
// semantic forbids any kind of read() before calling peekConsume()
|
||||
virtual const char* peekBuffer () override { return _ctx->peekBuffer(); }
|
||||
|
||||
// consume bytes after use (see peekBuffer)
|
||||
virtual void peekConsume (size_t consume) override { return _ctx->peekConsume(consume); }
|
||||
|
||||
private:
|
||||
std::shared_ptr<WiFiClientSecureCtx> _ctx;
|
||||
|
||||
|
@ -29,7 +29,8 @@ typedef void (*discard_cb_t)(void*, ClientContext*);
|
||||
extern "C" void esp_yield();
|
||||
extern "C" void esp_schedule();
|
||||
|
||||
#include "DataSource.h"
|
||||
#include <assert.h>
|
||||
#include <StreamDev.h>
|
||||
|
||||
bool getDefaultPrivateGlobalSyncValue ();
|
||||
|
||||
@ -374,7 +375,8 @@ public:
|
||||
if (!_pcb) {
|
||||
return 0;
|
||||
}
|
||||
return _write_from_source(new BufferDataSource(data, size));
|
||||
StreamConstPtr ptr(data, size);
|
||||
return _write_from_source(&ptr);
|
||||
}
|
||||
|
||||
size_t write(Stream& stream)
|
||||
@ -382,7 +384,7 @@ public:
|
||||
if (!_pcb) {
|
||||
return 0;
|
||||
}
|
||||
return _write_from_source(new BufferedStreamDataSource<Stream>(stream, stream.available()));
|
||||
return _write_from_source(&stream);
|
||||
}
|
||||
|
||||
size_t write_P(PGM_P buf, size_t size)
|
||||
@ -390,8 +392,8 @@ public:
|
||||
if (!_pcb) {
|
||||
return 0;
|
||||
}
|
||||
ProgmemStream stream(buf, size);
|
||||
return _write_from_source(new BufferedStreamDataSource<ProgmemStream>(stream, size));
|
||||
StreamConstPtr ptr(buf, size);
|
||||
return _write_from_source(&ptr);
|
||||
}
|
||||
|
||||
void keepAlive (uint16_t idle_sec = TCP_DEFAULT_KEEPALIVE_IDLE_SEC, uint16_t intv_sec = TCP_DEFAULT_KEEPALIVE_INTERVAL_SEC, uint8_t count = TCP_DEFAULT_KEEPALIVE_COUNT)
|
||||
@ -436,6 +438,29 @@ public:
|
||||
_sync = sync;
|
||||
}
|
||||
|
||||
// return a pointer to available data buffer (size = peekAvailable())
|
||||
// semantic forbids any kind of read() before calling peekConsume()
|
||||
const char* peekBuffer ()
|
||||
{
|
||||
if (!_rx_buf)
|
||||
return nullptr;
|
||||
return (const char*)_rx_buf->payload + _rx_buf_offset;
|
||||
}
|
||||
|
||||
// return number of byte accessible by peekBuffer()
|
||||
size_t peekAvailable ()
|
||||
{
|
||||
if (!_rx_buf)
|
||||
return 0;
|
||||
return _rx_buf->len - _rx_buf_offset;
|
||||
}
|
||||
|
||||
// consume bytes after use (see peekBuffer)
|
||||
void peekConsume (size_t consume)
|
||||
{
|
||||
_consume(consume);
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
bool _is_timeout()
|
||||
@ -452,7 +477,7 @@ protected:
|
||||
}
|
||||
}
|
||||
|
||||
size_t _write_from_source(DataSource* ds)
|
||||
size_t _write_from_source(Stream* ds)
|
||||
{
|
||||
assert(_datasource == nullptr);
|
||||
assert(!_send_waiting);
|
||||
@ -468,7 +493,6 @@ protected:
|
||||
if (_is_timeout()) {
|
||||
DEBUGV(":wtmo\r\n");
|
||||
}
|
||||
delete _datasource;
|
||||
_datasource = nullptr;
|
||||
break;
|
||||
}
|
||||
@ -495,20 +519,20 @@ protected:
|
||||
return false;
|
||||
}
|
||||
|
||||
DEBUGV(":wr %d %d\r\n", _datasource->available(), _written);
|
||||
DEBUGV(":wr %d %d\r\n", _datasource->peekAvailable(), _written);
|
||||
|
||||
bool has_written = false;
|
||||
|
||||
while (_datasource) {
|
||||
if (state() == CLOSED)
|
||||
return false;
|
||||
size_t next_chunk_size = std::min((size_t)tcp_sndbuf(_pcb), _datasource->available());
|
||||
size_t next_chunk_size = std::min((size_t)tcp_sndbuf(_pcb), _datasource->peekAvailable());
|
||||
if (!next_chunk_size)
|
||||
break;
|
||||
const uint8_t* buf = _datasource->get_buffer(next_chunk_size);
|
||||
const char* buf = _datasource->peekBuffer();
|
||||
|
||||
uint8_t flags = 0;
|
||||
if (next_chunk_size < _datasource->available())
|
||||
if (next_chunk_size < _datasource->peekAvailable())
|
||||
// PUSH is meant for peer, telling to give data to user app as soon as received
|
||||
// PUSH "may be set" when sender has finished sending a "meaningful" data block
|
||||
// PUSH does not break Nagle
|
||||
@ -522,15 +546,15 @@ protected:
|
||||
|
||||
err_t err = tcp_write(_pcb, buf, next_chunk_size, flags);
|
||||
|
||||
DEBUGV(":wrc %d %d %d\r\n", next_chunk_size, _datasource->available(), (int)err);
|
||||
DEBUGV(":wrc %d %d %d\r\n", next_chunk_size, _datasource->peekAvailable(), (int)err);
|
||||
|
||||
if (err == ERR_OK) {
|
||||
_datasource->release_buffer(buf, next_chunk_size);
|
||||
_datasource->peekConsume(next_chunk_size);
|
||||
_written += next_chunk_size;
|
||||
has_written = true;
|
||||
} else {
|
||||
// ERR_MEM(-1) is a valid error meaning
|
||||
// "come back later". It leaves state() opened
|
||||
// ERR_MEM(-1) is a valid error meaning
|
||||
// "come back later". It leaves state() opened
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -565,8 +589,6 @@ protected:
|
||||
|
||||
void _consume(size_t size)
|
||||
{
|
||||
if(_pcb)
|
||||
tcp_recved(_pcb, size);
|
||||
ptrdiff_t left = _rx_buf->len - _rx_buf_offset - size;
|
||||
if(left > 0) {
|
||||
_rx_buf_offset += size;
|
||||
@ -583,6 +605,8 @@ protected:
|
||||
pbuf_ref(_rx_buf);
|
||||
pbuf_free(head);
|
||||
}
|
||||
if(_pcb)
|
||||
tcp_recved(_pcb, size);
|
||||
}
|
||||
|
||||
err_t _recv(tcp_pcb* pcb, pbuf* pb, err_t err)
|
||||
@ -683,7 +707,7 @@ private:
|
||||
discard_cb_t _discard_cb;
|
||||
void* _discard_cb_arg;
|
||||
|
||||
DataSource* _datasource = nullptr;
|
||||
Stream* _datasource = nullptr;
|
||||
size_t _written = 0;
|
||||
uint32_t _timeout_ms = 5000;
|
||||
uint32_t _op_start_time = 0;
|
||||
|
@ -1,154 +0,0 @@
|
||||
/* DataSource.h - a read-only object similar to Stream, but with less methods
|
||||
* Copyright (c) 2016 Ivan Grokhotkov. All rights reserved.
|
||||
* This file is distributed under MIT license.
|
||||
*/
|
||||
#ifndef DATASOURCE_H
|
||||
#define DATASOURCE_H
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
class DataSource {
|
||||
public:
|
||||
virtual ~DataSource() {}
|
||||
virtual size_t available() = 0;
|
||||
virtual const uint8_t* get_buffer(size_t size) = 0;
|
||||
virtual void release_buffer(const uint8_t* buffer, size_t size) = 0;
|
||||
|
||||
};
|
||||
|
||||
class BufferDataSource : public DataSource {
|
||||
public:
|
||||
BufferDataSource(const uint8_t* data, size_t size) :
|
||||
_data(data),
|
||||
_size(size)
|
||||
{
|
||||
}
|
||||
|
||||
size_t available() override
|
||||
{
|
||||
return _size - _pos;
|
||||
}
|
||||
|
||||
const uint8_t* get_buffer(size_t size) override
|
||||
{
|
||||
(void)size;
|
||||
assert(_pos + size <= _size);
|
||||
return _data + _pos;
|
||||
}
|
||||
|
||||
void release_buffer(const uint8_t* buffer, size_t size) override
|
||||
{
|
||||
(void)buffer;
|
||||
assert(buffer == _data + _pos);
|
||||
_pos += size;
|
||||
}
|
||||
|
||||
protected:
|
||||
const uint8_t* _data;
|
||||
const size_t _size;
|
||||
size_t _pos = 0;
|
||||
};
|
||||
|
||||
template<typename TStream>
|
||||
class BufferedStreamDataSource : public DataSource {
|
||||
public:
|
||||
BufferedStreamDataSource(TStream& stream, size_t size) :
|
||||
_stream(stream),
|
||||
_size(size)
|
||||
{
|
||||
}
|
||||
|
||||
size_t available() override
|
||||
{
|
||||
return _size - _pos;
|
||||
}
|
||||
|
||||
const uint8_t* get_buffer(size_t size) override
|
||||
{
|
||||
assert(_pos + size <= _size);
|
||||
|
||||
//Data that was already read from the stream but not released (e.g. if tcp_write error occured). Otherwise this should be 0.
|
||||
const size_t stream_read = _streamPos - _pos;
|
||||
|
||||
//Min required buffer size: max(requested size, previous stream data already in buffer)
|
||||
const size_t min_buffer_size = size > stream_read ? size : stream_read;
|
||||
|
||||
//Buffer too small?
|
||||
if (_bufferSize < min_buffer_size) {
|
||||
uint8_t *new_buffer = new uint8_t[min_buffer_size];
|
||||
//If stream reading is ahead, than some data is already in the old buffer and needs to be copied to new resized buffer
|
||||
if (_buffer && stream_read > 0) {
|
||||
memcpy(new_buffer, _buffer.get(), stream_read);
|
||||
}
|
||||
_buffer.reset(new_buffer);
|
||||
_bufferSize = min_buffer_size;
|
||||
}
|
||||
|
||||
//Fetch remaining data from stream
|
||||
//If error in tcp_write in ClientContext::_write_some() occured earlier and therefore release_buffer was not called last time, than the requested stream data is already in the buffer.
|
||||
if (size > stream_read) {
|
||||
//Remaining bytes to read from stream
|
||||
const size_t stream_rem = size - stream_read;
|
||||
const size_t cb = _stream.readBytes(reinterpret_cast<char*>(_buffer.get() + stream_read), stream_rem);
|
||||
assert(cb == stream_rem);
|
||||
(void)cb;
|
||||
_streamPos += stream_rem;
|
||||
}
|
||||
return _buffer.get();
|
||||
|
||||
}
|
||||
|
||||
void release_buffer(const uint8_t* buffer, size_t size) override
|
||||
{
|
||||
if (size == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
(void)buffer;
|
||||
_pos += size;
|
||||
|
||||
//Cannot release more than acquired through get_buffer
|
||||
assert(_pos <= _streamPos);
|
||||
|
||||
//Release less than requested with get_buffer?
|
||||
if (_pos < _streamPos) {
|
||||
// Move unreleased stream data in buffer to front
|
||||
assert(_buffer);
|
||||
memmove(_buffer.get(), _buffer.get() + size, _streamPos - _pos);
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
TStream & _stream;
|
||||
std::unique_ptr<uint8_t[]> _buffer;
|
||||
size_t _size;
|
||||
size_t _pos = 0;
|
||||
size_t _bufferSize = 0;
|
||||
size_t _streamPos = 0;
|
||||
};
|
||||
|
||||
class ProgmemStream
|
||||
{
|
||||
public:
|
||||
ProgmemStream(PGM_P buf, size_t size) :
|
||||
_buf(buf),
|
||||
_left(size)
|
||||
{
|
||||
}
|
||||
|
||||
size_t readBytes(char* dst, size_t size)
|
||||
{
|
||||
size_t will_read = (_left < size) ? _left : size;
|
||||
memcpy_P((void*)dst, (PGM_VOID_P)_buf, will_read);
|
||||
_left -= will_read;
|
||||
_buf += will_read;
|
||||
return will_read;
|
||||
}
|
||||
|
||||
protected:
|
||||
PGM_P _buf;
|
||||
size_t _left;
|
||||
};
|
||||
|
||||
|
||||
#endif //DATASOURCE_H
|
@ -379,7 +379,7 @@ public:
|
||||
return result;
|
||||
}
|
||||
|
||||
size_t read(uint8_t* buf, size_t size) override {
|
||||
int read(uint8_t* buf, size_t size) override {
|
||||
if (!_opened || !_fd | !buf) {
|
||||
return 0;
|
||||
}
|
||||
|
@ -75,7 +75,7 @@ private:
|
||||
|
||||
WiFiClient tcpDumpClient;
|
||||
char* packetBuffer = nullptr;
|
||||
size_t bufferIndex = 0;
|
||||
int bufferIndex = 0;
|
||||
|
||||
static constexpr int tcpBufferSize = 2048;
|
||||
static constexpr int maxPcapLength = 1024;
|
||||
|
@ -287,7 +287,7 @@ public:
|
||||
return _opened ? _fd->write(buf, size) : -1;
|
||||
}
|
||||
|
||||
size_t read(uint8_t* buf, size_t size) override
|
||||
int read(uint8_t* buf, size_t size) override
|
||||
{
|
||||
return _opened ? _fd->read(buf, size) : -1;
|
||||
}
|
||||
|
188
libraries/esp8266/examples/StreamString/StreamString.ino
Normal file
188
libraries/esp8266/examples/StreamString/StreamString.ino
Normal file
@ -0,0 +1,188 @@
|
||||
|
||||
// this example sketch in the public domain is also a host and device test
|
||||
|
||||
#include <StreamDev.h>
|
||||
#include <StreamString.h>
|
||||
|
||||
void loop() {
|
||||
delay(1000);
|
||||
}
|
||||
|
||||
void checksketch(const char* what, const char* res1, const char* res2) {
|
||||
if (strcmp(res1, res2) == 0) {
|
||||
Serial << "PASSED: Test " << what << " (result: '" << res1 << "')\n";
|
||||
} else {
|
||||
Serial << "FAILED: Test " << what << ": '" << res1 << "' <> '" << res2 << "' !\n";
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef check
|
||||
#define check(what, res1, res2) checksketch(what, res1, res2)
|
||||
#endif
|
||||
|
||||
void testStringPtrProgmem() {
|
||||
static const char inProgmem [] PROGMEM = "I am in progmem";
|
||||
auto inProgmem2 = F("I am too in progmem");
|
||||
|
||||
int heap = (int)ESP.getFreeHeap();
|
||||
auto stream1 = StreamConstPtr(inProgmem, sizeof(inProgmem) - 1);
|
||||
auto stream2 = StreamConstPtr(inProgmem2);
|
||||
Serial << stream1 << " - " << stream2 << "\n";
|
||||
heap -= (int)ESP.getFreeHeap();
|
||||
check("NO heap occupation while streaming progmem strings", String(heap).c_str(), "0");
|
||||
}
|
||||
|
||||
void testStreamString() {
|
||||
String inputString = "hello";
|
||||
StreamString result;
|
||||
|
||||
// By default, reading a S2Stream(String) or a StreamString will consume the String.
|
||||
// It can be disabled by calling ::resetPointer(), (not default)
|
||||
// and reenabled by calling ::setConsume(). (default)
|
||||
//
|
||||
// In default consume mode, reading a byte or a block will remove it from
|
||||
// the String. Operations are O(n²).
|
||||
//
|
||||
// In non-default non-consume mode, it will just move a pointer. That one
|
||||
// can be ::resetPointer(pos) anytime. See the example below.
|
||||
|
||||
|
||||
// The String included in 'result' will not be modified by read:
|
||||
// (this is not the default)
|
||||
result.resetPointer();
|
||||
|
||||
{
|
||||
// We use a a lighter StreamConstPtr(input) to make a read-only Stream out of
|
||||
// a String that obviously should not be modified during the time the
|
||||
// StreamConstPtr instance is used. It is used as a source to be sent to
|
||||
// 'result'.
|
||||
|
||||
result.clear();
|
||||
StreamConstPtr(inputString).sendAll(result);
|
||||
StreamConstPtr(inputString).sendAll(result);
|
||||
StreamConstPtr(inputString).sendAll(result);
|
||||
check("StreamConstPtr.sendAll(StreamString)", result.c_str(), "hellohellohello");
|
||||
}
|
||||
|
||||
{
|
||||
// equivalent of the above
|
||||
|
||||
result.clear();
|
||||
result << inputString;
|
||||
result << inputString << inputString;
|
||||
check("StreamString<<String", result.c_str(), "hellohellohello");
|
||||
}
|
||||
|
||||
{
|
||||
// Now inputString is made into a Stream using S2Stream,
|
||||
// and set in non-consume mode (using ::resetPointer()).
|
||||
|
||||
// Then, after that input is read once, it won't be anymore readable
|
||||
// until the pointer is reset.
|
||||
|
||||
S2Stream input(inputString);
|
||||
input.resetPointer();
|
||||
|
||||
result.clear();
|
||||
input.sendAll(result);
|
||||
input.sendAll(result);
|
||||
check("S2Stream.sendAll(StreamString)", result.c_str(), "hello");
|
||||
check("unmodified String given to S2Stream", inputString.c_str(), "hello");
|
||||
}
|
||||
|
||||
{
|
||||
// Same as above, with an offset
|
||||
|
||||
result.clear();
|
||||
S2Stream input(inputString);
|
||||
// stream position set to offset 2 (0 by default)
|
||||
input.resetPointer(2);
|
||||
|
||||
input.sendAll(result);
|
||||
input.sendAll(result);
|
||||
check("S2Stream.resetPointer(2):", result.c_str(), "llo");
|
||||
}
|
||||
|
||||
{
|
||||
// inputString made into a Stream
|
||||
// reading the Stream consumes the String
|
||||
|
||||
result.clear();
|
||||
S2Stream input(inputString);
|
||||
// reading stream will consume the string
|
||||
input.setConsume(); // can be ommitted, this is the default
|
||||
|
||||
input.sendSize(result, 1);
|
||||
input.sendSize(result, 2);
|
||||
check("setConsume(): S2Stream().sendSize(StreamString,3)", result.c_str(), "hel");
|
||||
check("setConsume(): String given from S2Stream is swallowed", inputString.c_str(), "lo");
|
||||
}
|
||||
|
||||
// Streaming with common String constructors
|
||||
{
|
||||
StreamString cons(inputString);
|
||||
check("StreamString(String)", cons.c_str(), inputString.c_str());
|
||||
}
|
||||
{
|
||||
StreamString cons(result);
|
||||
check("StreamString(char*)", cons.c_str(), result.c_str());
|
||||
}
|
||||
{
|
||||
StreamString cons("abc");
|
||||
check("StreamString(char*)", cons.c_str(), "abc");
|
||||
}
|
||||
{
|
||||
StreamString cons(F("abc"));
|
||||
check("StreamString(F())", cons.c_str(), "abc");
|
||||
}
|
||||
{
|
||||
StreamString cons(23);
|
||||
check("StreamString(int)", cons.c_str(), "23");
|
||||
}
|
||||
{
|
||||
StreamString cons('a');
|
||||
check("StreamString(char)", cons.c_str(), "a");
|
||||
}
|
||||
{
|
||||
StreamString cons(23.2);
|
||||
check("StreamString(float)", cons.c_str(), "23.20");
|
||||
}
|
||||
|
||||
#if !CORE_MOCK
|
||||
|
||||
// A progmem won't use Heap when StringPtr is used
|
||||
testStringPtrProgmem();
|
||||
|
||||
// .. but it does when S2Stream or StreamString is used
|
||||
{
|
||||
int heap = (int)ESP.getFreeHeap();
|
||||
auto stream = StreamString(F("I am in progmem"));
|
||||
Serial << stream << "\n";
|
||||
heap -= (int)ESP.getFreeHeap();
|
||||
String heapStr(heap);
|
||||
if (heap != 0) {
|
||||
check("heap is occupied by String/StreamString(progmem)", heapStr.c_str(), heapStr.c_str());
|
||||
} else {
|
||||
check("ERROR: heap should be occupied by String/StreamString(progmem)", heapStr.c_str(), "-1");
|
||||
}
|
||||
}
|
||||
|
||||
// (check again to be sure)
|
||||
testStringPtrProgmem();
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifndef TEST_CASE
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
delay(1000);
|
||||
|
||||
testStreamString();
|
||||
|
||||
Serial.printf("sizeof: String:%d Stream:%d StreamString:%d SStream:%d\n",
|
||||
(int)sizeof(String), (int)sizeof(Stream), (int)sizeof(StreamString), (int)sizeof(S2Stream));
|
||||
}
|
||||
|
||||
#endif
|
Reference in New Issue
Block a user