Add EventSource plugin
This commit is contained in:
parent
dce6d35ad4
commit
cdd1ced67a
25
README.md
25
README.md
|
@ -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
|
- Easily extendible to handle any type of content
|
||||||
- Supports Continue 100
|
- Supports Continue 100
|
||||||
- Async WebSocket plugin offering different locations without extra servers or ports
|
- 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
|
## Important things to remember
|
||||||
- This is fully asynchronous server and as such does not run on the loop thread.
|
- 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
|
## 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
|
### Basic response with HTTP Code
|
||||||
```cpp
|
```cpp
|
||||||
|
@ -656,6 +667,10 @@ const uint8_t flash_binary[] PROGMEM = { 0x01, 0x02, 0x03, 0x04 };
|
||||||
client->binary(flash_binary, 4);
|
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
|
## Setting up the server
|
||||||
```cpp
|
```cpp
|
||||||
|
@ -664,6 +679,7 @@ client->binary(flash_binary, 4);
|
||||||
|
|
||||||
AsyncWebServer server(80);
|
AsyncWebServer server(80);
|
||||||
AsyncWebSocket ws("/ws"); // access at ws://[esp ip]/ws
|
AsyncWebSocket ws("/ws"); // access at ws://[esp ip]/ws
|
||||||
|
AsyncEventSource events("/events"); // event source (server side events)
|
||||||
|
|
||||||
const char* ssid = "your-ssid";
|
const char* ssid = "your-ssid";
|
||||||
const char* password = "your-pass";
|
const char* password = "your-pass";
|
||||||
|
@ -700,6 +716,9 @@ void setup(){
|
||||||
ws.onEvent(onEvent);
|
ws.onEvent(onEvent);
|
||||||
server.addHandler(&ws);
|
server.addHandler(&ws);
|
||||||
|
|
||||||
|
// attach AsyncEventSource
|
||||||
|
server.addHandler(&events);
|
||||||
|
|
||||||
// respond to GET requests on URL /heap
|
// respond to GET requests on URL /heap
|
||||||
server.on("/heap", HTTP_GET, [](AsyncWebServerRequest *request){
|
server.on("/heap", HTTP_GET, [](AsyncWebServerRequest *request){
|
||||||
request->send(200, "text/plain", String(ESP.getFreeHeap()));
|
request->send(200, "text/plain", String(ESP.getFreeHeap()));
|
||||||
|
@ -735,7 +754,11 @@ void setup(){
|
||||||
server.begin();
|
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
|
### Methods for controlling websocket connections
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
|
@ -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_ */
|
|
@ -378,5 +378,6 @@ class AsyncWebServer {
|
||||||
#include "WebResponseImpl.h"
|
#include "WebResponseImpl.h"
|
||||||
#include "WebHandlerImpl.h"
|
#include "WebHandlerImpl.h"
|
||||||
#include "AsyncWebSocket.h"
|
#include "AsyncWebSocket.h"
|
||||||
|
#include "AsyncEventSource.h"
|
||||||
|
|
||||||
#endif /* _AsyncWebServer_H_ */
|
#endif /* _AsyncWebServer_H_ */
|
||||||
|
|
Loading…
Reference in New Issue