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

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_ */