1
0
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:
david gauchard
2021-03-15 01:36:20 +01:00
committed by GitHub
parent 4cc1472821
commit c720c0d9e8
48 changed files with 2136 additions and 650 deletions

View File

@ -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

View File

@ -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:

View File

@ -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) {

View File

@ -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) {

View File

@ -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) {

View File

@ -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>

View 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;
}
}
}

View 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()

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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);

View File

@ -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)) {

View File

@ -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;

View File

@ -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;

View File

@ -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

View File

@ -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;
}

View File

@ -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;

View File

@ -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;
}

View 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