Skip to content

Commit

Permalink
Feature: SmtpClient (#1380)
Browse files Browse the repository at this point in the history
A powerful asynchronous smtp client that allows sending emails directly from ESP8266.
- support for authentication (PLAIN and CRAM-MD5)
- support for faster transfer via PIPELINING
- support for attachments ( as much and as big as the remote server allows)
- support for UTF-8 text encoding
- support for sending multiple emails using the same TCP connection
- support for connection over SSL and support for STARTTLS (needs Sming to be compiled with ENABLE_SSL=1 )
slaff authored May 16, 2018
1 parent 0b56196 commit 0d1ec41
Showing 13 changed files with 1,168 additions and 84 deletions.
56 changes: 47 additions & 9 deletions Readme.md
Original file line number Diff line number Diff line change
@@ -19,16 +19,17 @@ Sming - Open Source framework for high efficiency WiFi SoC ESP8266 native develo
* Built-in JSON library: [ArduinoJson](https://github.com/bblanchon/ArduinoJson)
* HTTP, AJAX, WebSockets support
* MQTT protocol based on [libemqtt](https://github.com/menudoproblema/libemqtt)
* Powerful SmtpClient with support for STARTTLS, PIPELINE, PLAIN and CRAM-MD5 authentication, sending attachments and more.
* Networking based on LWIP stack
* Simple and powerful hardware API wrappers
* Crash handlers for analyzing/handling system restarts due to fatal errors or WDT resets.
* SSL support based on [axTLS 2.1+](https://github.com/igrr/axtls-8266) with [Lwirax](https://github.com/attachix/lwirax/).
* Out of the box support for HTTP, MQTT and Websocket client connections over SSL.
* Out of the box support for HTTP, MQTT and Websocket client connections over SSL.
* Out of the box support for OTA over HTTPS.
* [SNI](https://tools.ietf.org/html/rfc6066#page-6) and [Maximum Fragment Length](https://tools.ietf.org/html/rfc6066#page-8) SSL support.
* PWM support based on [Stefan Bruens PWM](https://github.com/StefanBruens/ESP8266_new_pwm.git)
* Optional custom heap allocation based on [Umm Malloc](https://github.com/rhempel/umm_malloc.git)
* Based on Espressif NONOS SDK. Tested with versions 1.4, 1.5 and 2.0.
* Based on Espressif NONOS SDK. Tested with versions 1.4, 1.5 and 2.0.

## Compatibility

@@ -53,7 +54,7 @@ n/a = The selected SDK is not available on that OS


## Additional needed software
- [ESPtool2](https://github.com/raburton/esptool2) esptool2
- [ESPtool2](https://github.com/raburton/esptool2) esptool2

## Optional features

@@ -144,21 +145,21 @@ void onDataSent(HttpClient& client, bool successful)
}
```
For more examples take a look at the [HttpClient](samples/HttpClient/app/application.cpp), [HttpClient_Instapush](samples/HttpClient_Instapush/app/application.cpp) and [HttpClient_ThingSpeak](samples/HttpClient_ThingSpeak/app/application.cpp) samples.
For more examples take a look at the [HttpClient](samples/HttpClient/app/application.cpp), [HttpClient_Instapush](samples/HttpClient_Instapush/app/application.cpp) and [HttpClient_ThingSpeak](samples/HttpClient_ThingSpeak/app/application.cpp) samples.
### OTA application update based on rBoot
```c++
void OtaUpdate() {
uint8 slot;
rboot_config bootconf;
Serial.println("Updating...");
// need a clean object, otherwise if run before and failed will not run again
if (otaUpdater) delete otaUpdater;
otaUpdater = new rBootHttpUpdate();
// select rom slot to flash
bootconf = rboot_get_config();
slot = bootconf.current_rom;
@@ -202,12 +203,49 @@ void onFile(HttpRequest &request, HttpResponse &response)
String file = request.getPath();
if (file[0] == '/')
file = file.substring(1);

response.setCache(86400, true);
response.sendFile(file);
}
```
### SmtpClient for sending emails
```c++
SmtpClient emailClient;
emailClient.connect(URL("smtp://user:[email protected]:25"));
MailMessage* mail = new MailMessage();
mail->from = "developers@sming";
mail->to = "iot-developers@world";
mail->subject = "Greetings from Sming";
mail->setBody("Hello");
FileStream* file= new FileStream("image.png");
mail->addAttachment(file);
emailClient.onMessageSent(onMailSent);
emailClient.send(mail);
...
int onMailSent(SmtpClient& client, int code, char* status)
{
MailMessage* mail = client.getCurrentMessage();
...
if(!client.countPending()) {
client.quit();
}
return 0;
}
```

See the [SmtpCient sample](samples/SmtpClient/app/application.cpp) for details.

### Documentation
A complete documentation can be created by running the command below. This requires `doxygen` to be installed on your system.

87 changes: 40 additions & 47 deletions Sming/SmingCore/Network/Http/HttpConnection.cpp
Original file line number Diff line number Diff line change
@@ -404,72 +404,65 @@ int HttpConnection::staticOnChunkComplete(http_parser* parser)

void HttpConnection::onReadyToSendData(TcpConnectionEvent sourceEvent)
{

debug_d("HttpConnection::onReadyToSendData: waitingQueue.count: %d", waitingQueue->count());

do {
if(state == eHCS_Sent) {
state = eHCS_Ready;
REENTER:
switch(state) {
case eHCS_Ready: {
HttpRequest* request = waitingQueue->peek();
if(request == NULL) {
debug_d("Nothing in the waiting queue");
outgoingRequest = NULL;
break;
}

if(state == eHCS_Ready) {
HttpRequest* request = waitingQueue->peek();
if(request == NULL) {
debug_d("Nothing in the waiting queue");
outgoingRequest = NULL;
// if the executionQueue is not empty then we have to check if we can pipeline that request
if(executionQueue.count()) {
if(!(request->method == HTTP_GET || request->method == HTTP_HEAD)) {
// if the current request cannot be pipelined -> break;
break;
}

// if the executionQueue is not empty then we have to check if we can pipeline that request
if(executionQueue.count()) {
if(!(request->method == HTTP_GET || request->method == HTTP_HEAD)) {
// if the current request cannot be pipelined -> break;
// if we have previous request
if(outgoingRequest != NULL) {
if(!(outgoingRequest->method == HTTP_GET || outgoingRequest->method == HTTP_HEAD)) {
// the outgoing request does not allow pipelining
break;
}

// if we have previous request
if(outgoingRequest != NULL) {
if(!(outgoingRequest->method == HTTP_GET || outgoingRequest->method == HTTP_HEAD)) {
// the outgoing request does not allow pipelining
break;
}
}
} // executionQueue.count()

if(!executionQueue.enqueue(request)) {
debug_e("The working queue is full at the moment");
break;
}
} // executionQueue.count()

waitingQueue->dequeue();

outgoingRequest = request;
state = eHCS_SendingHeaders;
sendRequestHeaders(request);

if(!executionQueue.enqueue(request)) {
debug_e("The working queue is full at the moment");
break;
}

if(state >= eHCS_StartSending && state < eHCS_Sent) {
if(state == eHCS_SendingHeaders) {
if(stream != NULL && !stream->isFinished()) {
break;
}
waitingQueue->dequeue();

state = eHCS_StartBody;
}
outgoingRequest = request;
sendRequestHeaders(request);

if(sendRequestBody(outgoingRequest)) {
state = eHCS_Sent;
delete stream;
stream = NULL;
continue;
}
state = eHCS_SendingHeaders;
}

case eHCS_SendingHeaders: {
if(stream != NULL && !stream->isFinished()) {
break;
}

break;
state = eHCS_StartBody;
}

} while(true);
case eHCS_StartBody:
case eHCS_SendingBody: {
if(sendRequestBody(outgoingRequest)) {
state = eHCS_Ready;
delete stream;
stream = NULL;
goto REENTER;
}
}
} // switch(state)

TcpClient::onReadyToSendData(sourceEvent);
}
52 changes: 24 additions & 28 deletions Sming/SmingCore/Network/Http/HttpServerConnection.cpp
Original file line number Diff line number Diff line change
@@ -14,7 +14,6 @@

#include "HttpServer.h"
#include "TcpServer.h"
#include "../../Services/cWebsocket/websocket.h"
#include "WebConstants.h"
#include "../../Data/Stream/ChunkedStream.h"

@@ -370,48 +369,45 @@ err_t HttpServerConnection::onReceive(pbuf *buf)

void HttpServerConnection::onReadyToSendData(TcpConnectionEvent sourceEvent)
{
if(state == eHCS_Sent) {
state = eHCS_Ready;
switch(state) {
case eHCS_StartSending: {
sendResponseHeaders(&response);
state = eHCS_SendingHeaders;
}

do {

if(!(state >= eHCS_StartSending && state < eHCS_Sent)) {
case eHCS_SendingHeaders: {
if (stream != NULL && !stream->isFinished()) {
break;
}

if(state == eHCS_StartSending) {
sendResponseHeaders(&response);
state = eHCS_SendingHeaders;
state = eHCS_StartBody;
}

case eHCS_StartBody:
case eHCS_SendingBody: {
if(!sendResponseBody(&response)) {
break;
}

if(state == eHCS_SendingHeaders) {
if(stream != NULL && !stream->isFinished()) {
break;
}

state = eHCS_StartBody;
}
delete stream;
stream = NULL;
state = eHCS_Sent;
}

if(sendResponseBody(&response)) {
delete stream;
stream = NULL;
state = eHCS_Sent;
case eHCS_Sent: {
if(response.headers["Connection"] == "close") {
setTimeOut(1); // decrease the timeout to 1 tick
}

break;
response.reset();
request.reset();

} while(false);
state = eHCS_Ready;

if(state == eHCS_Sent && response.headers["Connection"] == "close") {
setTimeOut(1); // decrease the timeout to 1 tick
break;
}

if(state == eHCS_Sent) {
response.reset();
request.reset();
}
} /* switch(state) */

TcpClient::onReadyToSendData(sourceEvent);
}
Loading

0 comments on commit 0d1ec41

Please sign in to comment.