Add EventSource plugin

This commit is contained in:
Me No Dev 2016-06-28 21:51:48 +03:00
parent dce6d35ad4
commit cdd1ced67a
4 changed files with 367 additions and 6 deletions

View File

@ -18,6 +18,9 @@ To use this library you need to have the latest git versions of either [ESP8266]
- Easily extendible to handle any type of content
- Supports Continue 100
- Async WebSocket plugin offering different locations without extra servers or ports
- Async EventSource (ServerSideEvents) plugin to send events to the browser
- URL Rewrite plugin for conditional and permanent url rewrites
- ServeStatic plugin that supports cache, Last-Modified, default index and more
## Important things to remember
- This is fully asynchronous server and as such does not run on the loop thread.
@ -65,13 +68,13 @@ To use this library you need to have the latest git versions of either [ESP8266]
- Two filter callbacks are provided: ```ON_AP_FILTER``` to execute the rewrite when request is made to the AP interface,
```ON_SAT_FILTER``` to execute the rewrite when request is made to the STA interface.
- The ```canHandle``` method is used for handler specific control on whether the requests can be handled
and for declaring any interesting headers that the ```Request``` should parse. Decision can be based on request
and for declaring any interesting headers that the ```Request``` should parse. Decision can be based on request
method, request url, http version, request host/port/target host and get parameters
- Once a ```Handler``` is attached to given ```Request``` (```canHandle``` returned true)
that ```Handler``` takes care to receive any file/data upload and attach a ```Response```
once the ```Request``` has been fully parsed
- ```Handlers``` are evaluated in the order they are attached to the server. The ```canHandle``` is called only
if the ```Filter``` that was set to the ```Handler``` return true.
- ```Handlers``` are evaluated in the order they are attached to the server. The ```canHandle``` is called only
if the ```Filter``` that was set to the ```Handler``` return true.
- The first ```Handler``` that can handle the request is selected, not further ```Filter``` and ```canHandle``` are called.
### Responses and how do they work
@ -199,6 +202,14 @@ void handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_
```
## Responses
### Redirect to another URL
```cpp
//to local url
request->redirect("/login");
//to external url
request->redirect("http://esp8266.com");
```
### Basic response with HTTP Code
```cpp
@ -417,7 +428,7 @@ request->send(response);
## Serving static files
In addition to serving files from SPIFFS as described above, the server provide a dedicated handler that optimize the
performance of serving files from SPIFFS - ```AsyncStaticWebHandler```. Use ```server.serveStatic()``` function to
performance of serving files from SPIFFS - ```AsyncStaticWebHandler```. Use ```server.serveStatic()``` function to
initialize and add a new instance of ```AsyncStaticWebHandler``` to the server.
The Handler will not handle the request if the file does not exists, e.g. the server will continue to look for another
handler that can handle the request.
@ -656,6 +667,10 @@ const uint8_t flash_binary[] PROGMEM = { 0x01, 0x02, 0x03, 0x04 };
client->binary(flash_binary, 4);
```
## Async Event Source Plugin
The server includes EventSource (ServerSideEvents) plugin which can be used to send short text events to the browser.
Difference between EventSource and WebSockets is that EventSource is single direction, text-only protocol.
## Setting up the server
```cpp
@ -664,6 +679,7 @@ client->binary(flash_binary, 4);
AsyncWebServer server(80);
AsyncWebSocket ws("/ws"); // access at ws://[esp ip]/ws
AsyncEventSource events("/events"); // event source (server side events)
const char* ssid = "your-ssid";
const char* password = "your-pass";
@ -700,6 +716,9 @@ void setup(){
ws.onEvent(onEvent);
server.addHandler(&ws);
// attach AsyncEventSource
server.addHandler(&events);
// respond to GET requests on URL /heap
server.on("/heap", HTTP_GET, [](AsyncWebServerRequest *request){
request->send(200, "text/plain", String(ESP.getFreeHeap()));
@ -735,7 +754,11 @@ void setup(){
server.begin();
}
void loop(){}
void loop(){
static char temp[128];
sprintf(temp, "Seconds since boot: %u", millis()/1000);
events.send(temp, "time"); //send event "time"
}
```
### Methods for controlling websocket connections
@ -750,7 +773,7 @@ void loop(){}
ws.enable(true);
```
Example of OTA code
Example of OTA code
```arduino
// OTA callbacks

250
src/AsyncEventSource.cpp Normal file
View File

@ -0,0 +1,250 @@
/*
Asynchronous WebServer library for Espressif MCUs
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "Arduino.h"
#include "AsyncEventSource.h"
// Client
AsyncEventSourceClient::AsyncEventSourceClient(AsyncWebServerRequest *request, AsyncEventSource *server){
_client = request->client();
_server = server;
next = NULL;
//_client->onError([](void *r, AsyncClient* c, int8_t error){ ((AsyncEventSourceClient*)(r))->_onError(error); }, this);
//_client->onAck([](void *r, AsyncClient* c, size_t len, uint32_t time){ ((AsyncEventSourceClient*)(r))->_onAck(len, time); }, this);
//_client->onPoll([](void *r, AsyncClient* c){ ((AsyncEventSourceClient*)(r))->_onPoll(); }, this);
_client->onError(NULL, NULL);
_client->onAck(NULL, NULL);
_client->onPoll(NULL, NULL);
_client->onData(NULL, NULL);
_client->onTimeout([](void *r, AsyncClient* c, uint32_t time){ ((AsyncEventSourceClient*)(r))->_onTimeout(time); }, this);
_client->onDisconnect([](void *r, AsyncClient* c){ ((AsyncEventSourceClient*)(r))->_onDisconnect(); }, this);
_server->_addClient(this);
delete request;
}
AsyncEventSourceClient::~AsyncEventSourceClient(){
close();
}
//void AsyncEventSourceClient::_onAck(size_t len, uint32_t time){}
//void AsyncEventSourceClient::_onPoll(){}
//void AsyncEventSourceClient::_onError(int8_t){}
void AsyncEventSourceClient::_onTimeout(uint32_t time){
_client->close(true);
}
void AsyncEventSourceClient::_onDisconnect(){
AsyncClient* cl = _client;
_client = NULL;
cl->free();
delete cl;
_server->_handleDisconnect(this);
}
void AsyncEventSourceClient::close(){
if(_client != NULL)
_client->close(true);
}
void AsyncEventSourceClient::send(const char * message, size_t len){
if(!_client->canSend()){
return;
}
if(_client->space() < len){
return;
}
_client->write(message, len);
}
// Handler
AsyncEventSource::AsyncEventSource(String url):_url(url){}
AsyncEventSource::~AsyncEventSource(){
close();
}
void AsyncEventSource::_addClient(AsyncEventSourceClient * client){
if(_clients == NULL){
_clients = client;
return;
}
AsyncEventSourceClient * c = _clients;
while(c->next != NULL) c = c->next;
c->next = client;
}
void AsyncEventSource::_handleDisconnect(AsyncEventSourceClient * client){
if(_clients == NULL){
return;
}
if(_clients == client){
_clients = client->next;
delete client;
return;
}
AsyncEventSourceClient * c = _clients;
while(c->next != NULL && c->next != client) c = c->next;
if(c->next == NULL){
return;
}
c->next = client->next;
delete client;
}
void AsyncEventSource::close(){
AsyncEventSourceClient * c = _clients;
while(c != NULL){
if(c->connected())
c->close();
c = c->next;
}
}
void AsyncEventSource::send(const char *message, const char *event, uint32_t id, uint32_t reconnect){
if(_clients == NULL)
return;
String ev = "";
if(reconnect){
ev += "retry: ";
ev += String(reconnect);
ev += "\r\n";
}
if(id){
ev += "id: ";
ev += String(reconnect);
ev += "\r\n";
}
if(event != NULL){
ev += "event: ";
ev += String(event);
ev += "\r\n";
}
if(message != NULL){
size_t messageLen = strlen(message);
char * lineStart = (char *)message;
char * lineEnd;
do {
char * nextN = strchr(lineStart, '\n');
char * nextR = strchr(lineStart, '\r');
if(nextN == NULL && nextR == NULL){
//last line
size_t llen = ((char *)message + messageLen) - lineStart;
char * ldata = (char *)malloc(llen+1);
if(ldata != NULL){
memcpy(ldata, lineStart, llen);
ldata[llen] = 0;
ev += "data: ";
ev += ldata;
ev += "\r\n\r\n";
free(ldata);
}
lineStart = (char *)message + messageLen;
} else {
char * nextLine = NULL;
if(nextN != NULL && nextR != NULL){
if(nextR < nextN){
lineEnd = nextR;
if(nextN == (nextR + 1))
nextLine = nextN + 1;
else
nextLine = nextR + 1;
} else {
lineEnd = nextN;
if(nextR == (nextN + 1))
nextLine = nextR + 1;
else
nextLine = nextN + 1;
}
} else if(nextN != NULL){
lineEnd = nextN;
nextLine = nextN + 1;
} else {
lineEnd = nextR;
nextLine = nextR + 1;
}
size_t llen = lineEnd - lineStart;
char * ldata = (char *)malloc(llen+1);
if(ldata != NULL){
memcpy(ldata, lineStart, llen);
ldata[llen] = 0;
ev += "data: ";
ev += ldata;
ev += "\r\n";
free(ldata);
}
lineStart = nextLine;
if(lineStart == ((char *)message + messageLen))
ev += "\r\n";
}
} while(lineStart < ((char *)message + messageLen));
}
//os_printf("EVENT_SOURCE:\n%s", ev.c_str());
AsyncEventSourceClient * c = _clients;
while(c != NULL){
if(c->connected())
c->send(ev.c_str(), ev.length());
c = c->next;
}
ev = String();
}
bool AsyncEventSource::canHandle(AsyncWebServerRequest *request){
if(request->method() != HTTP_GET || !request->url().equals(_url))
return false;
return true;
}
void AsyncEventSource::handleRequest(AsyncWebServerRequest *request){
request->send(new AsyncEventSourceResponse(this));
}
// Response
AsyncEventSourceResponse::AsyncEventSourceResponse(AsyncEventSource *server){
_server = server;
_code = 200;
_contentType = "text/event-stream";
_sendContentLength = false;
addHeader("Cache-Control", "no-cache");
addHeader("Connection","keep-alive");
}
void AsyncEventSourceResponse::_respond(AsyncWebServerRequest *request){
String out = _assembleHead(request->version());
request->client()->write(out.c_str(), _headLength);
_state = RESPONSE_WAIT_ACK;
}
size_t AsyncEventSourceResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time){
if(len){
new AsyncEventSourceClient(request, _server);
}
return 0;
}

87
src/AsyncEventSource.h Normal file
View File

@ -0,0 +1,87 @@
/*
Asynchronous WebServer library for Espressif MCUs
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef ASYNCEVENTSOURCE_H_
#define ASYNCEVENTSOURCE_H_
#include <Arduino.h>
#include <ESPAsyncTCP.h>
#include <ESPAsyncWebServer.h>
class AsyncEventSource;
class AsyncEventSourceResponse;
class AsyncEventSourceClient;
class AsyncEventSourceClient {
private:
AsyncClient *_client;
AsyncEventSource *_server;
public:
AsyncEventSourceClient * next;
AsyncEventSourceClient(AsyncWebServerRequest *request, AsyncEventSource *server);
~AsyncEventSourceClient();
AsyncClient* client(){ return _client; }
void close();
void send(const char * message, size_t len);
bool connected(){ return (_client != NULL) && _client->connected(); }
//system callbacks (do not call)
//void _onAck(size_t len, uint32_t time);
//void _onError(int8_t);
//void _onPoll();
void _onTimeout(uint32_t time);
void _onDisconnect();
};
class AsyncEventSource: public AsyncWebHandler {
private:
String _url;
AsyncEventSourceClient * _clients;
uint32_t _cNextId;
public:
AsyncEventSource(String url);
~AsyncEventSource();
const char * url(){ return _url.c_str(); }
void close();
void send(const char *message, const char *event=NULL, uint32_t id=0, uint32_t reconnect=0);
//system callbacks (do not call)
void _addClient(AsyncEventSourceClient * client);
void _handleDisconnect(AsyncEventSourceClient * client);
bool canHandle(AsyncWebServerRequest *request);
void handleRequest(AsyncWebServerRequest *request);
};
class AsyncEventSourceResponse: public AsyncWebServerResponse {
private:
String _content;
AsyncEventSource *_server;
public:
AsyncEventSourceResponse(AsyncEventSource *server);
void _respond(AsyncWebServerRequest *request);
size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time);
bool _sourceValid(){ return true; }
};
#endif /* ASYNCEVENTSOURCE_H_ */

View File

@ -378,5 +378,6 @@ class AsyncWebServer {
#include "WebResponseImpl.h"
#include "WebHandlerImpl.h"
#include "AsyncWebSocket.h"
#include "AsyncEventSource.h"
#endif /* _AsyncWebServer_H_ */