Skip to content

Commit cdd1ced

Browse files
author
Me No Dev
committed
Add EventSource plugin
1 parent dce6d35 commit cdd1ced

File tree

4 files changed

+367
-6
lines changed

4 files changed

+367
-6
lines changed

README.md

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ To use this library you need to have the latest git versions of either [ESP8266]
1818
- Easily extendible to handle any type of content
1919
- Supports Continue 100
2020
- Async WebSocket plugin offering different locations without extra servers or ports
21+
- Async EventSource (ServerSideEvents) plugin to send events to the browser
22+
- URL Rewrite plugin for conditional and permanent url rewrites
23+
- ServeStatic plugin that supports cache, Last-Modified, default index and more
2124

2225
## Important things to remember
2326
- 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]
6568
- Two filter callbacks are provided: ```ON_AP_FILTER``` to execute the rewrite when request is made to the AP interface,
6669
```ON_SAT_FILTER``` to execute the rewrite when request is made to the STA interface.
6770
- The ```canHandle``` method is used for handler specific control on whether the requests can be handled
68-
and for declaring any interesting headers that the ```Request``` should parse. Decision can be based on request
71+
and for declaring any interesting headers that the ```Request``` should parse. Decision can be based on request
6972
method, request url, http version, request host/port/target host and get parameters
7073
- Once a ```Handler``` is attached to given ```Request``` (```canHandle``` returned true)
7174
that ```Handler``` takes care to receive any file/data upload and attach a ```Response```
7275
once the ```Request``` has been fully parsed
73-
- ```Handlers``` are evaluated in the order they are attached to the server. The ```canHandle``` is called only
74-
if the ```Filter``` that was set to the ```Handler``` return true.
76+
- ```Handlers``` are evaluated in the order they are attached to the server. The ```canHandle``` is called only
77+
if the ```Filter``` that was set to the ```Handler``` return true.
7578
- The first ```Handler``` that can handle the request is selected, not further ```Filter``` and ```canHandle``` are called.
7679

7780
### Responses and how do they work
@@ -199,6 +202,14 @@ void handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_
199202
```
200203

201204
## Responses
205+
### Redirect to another URL
206+
```cpp
207+
//to local url
208+
request->redirect("/login");
209+
210+
//to external url
211+
request->redirect("http://esp8266.com");
212+
```
202213
203214
### Basic response with HTTP Code
204215
```cpp
@@ -417,7 +428,7 @@ request->send(response);
417428

418429
## Serving static files
419430
In addition to serving files from SPIFFS as described above, the server provide a dedicated handler that optimize the
420-
performance of serving files from SPIFFS - ```AsyncStaticWebHandler```. Use ```server.serveStatic()``` function to
431+
performance of serving files from SPIFFS - ```AsyncStaticWebHandler```. Use ```server.serveStatic()``` function to
421432
initialize and add a new instance of ```AsyncStaticWebHandler``` to the server.
422433
The Handler will not handle the request if the file does not exists, e.g. the server will continue to look for another
423434
handler that can handle the request.
@@ -656,6 +667,10 @@ const uint8_t flash_binary[] PROGMEM = { 0x01, 0x02, 0x03, 0x04 };
656667
client->binary(flash_binary, 4);
657668
```
658669

670+
## Async Event Source Plugin
671+
The server includes EventSource (ServerSideEvents) plugin which can be used to send short text events to the browser.
672+
Difference between EventSource and WebSockets is that EventSource is single direction, text-only protocol.
673+
659674

660675
## Setting up the server
661676
```cpp
@@ -664,6 +679,7 @@ client->binary(flash_binary, 4);
664679

665680
AsyncWebServer server(80);
666681
AsyncWebSocket ws("/ws"); // access at ws://[esp ip]/ws
682+
AsyncEventSource events("/events"); // event source (server side events)
667683

668684
const char* ssid = "your-ssid";
669685
const char* password = "your-pass";
@@ -700,6 +716,9 @@ void setup(){
700716
ws.onEvent(onEvent);
701717
server.addHandler(&ws);
702718

719+
// attach AsyncEventSource
720+
server.addHandler(&events);
721+
703722
// respond to GET requests on URL /heap
704723
server.on("/heap", HTTP_GET, [](AsyncWebServerRequest *request){
705724
request->send(200, "text/plain", String(ESP.getFreeHeap()));
@@ -735,7 +754,11 @@ void setup(){
735754
server.begin();
736755
}
737756

738-
void loop(){}
757+
void loop(){
758+
static char temp[128];
759+
sprintf(temp, "Seconds since boot: %u", millis()/1000);
760+
events.send(temp, "time"); //send event "time"
761+
}
739762
```
740763
741764
### Methods for controlling websocket connections
@@ -750,7 +773,7 @@ void loop(){}
750773
ws.enable(true);
751774
```
752775

753-
Example of OTA code
776+
Example of OTA code
754777

755778
```arduino
756779
// OTA callbacks

src/AsyncEventSource.cpp

Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
/*
2+
Asynchronous WebServer library for Espressif MCUs
3+
4+
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
5+
6+
This library is free software; you can redistribute it and/or
7+
modify it under the terms of the GNU Lesser General Public
8+
License as published by the Free Software Foundation; either
9+
version 2.1 of the License, or (at your option) any later version.
10+
11+
This library is distributed in the hope that it will be useful,
12+
but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14+
Lesser General Public License for more details.
15+
16+
You should have received a copy of the GNU Lesser General Public
17+
License along with this library; if not, write to the Free Software
18+
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19+
*/
20+
#include "Arduino.h"
21+
#include "AsyncEventSource.h"
22+
23+
// Client
24+
25+
AsyncEventSourceClient::AsyncEventSourceClient(AsyncWebServerRequest *request, AsyncEventSource *server){
26+
_client = request->client();
27+
_server = server;
28+
next = NULL;
29+
//_client->onError([](void *r, AsyncClient* c, int8_t error){ ((AsyncEventSourceClient*)(r))->_onError(error); }, this);
30+
//_client->onAck([](void *r, AsyncClient* c, size_t len, uint32_t time){ ((AsyncEventSourceClient*)(r))->_onAck(len, time); }, this);
31+
//_client->onPoll([](void *r, AsyncClient* c){ ((AsyncEventSourceClient*)(r))->_onPoll(); }, this);
32+
_client->onError(NULL, NULL);
33+
_client->onAck(NULL, NULL);
34+
_client->onPoll(NULL, NULL);
35+
_client->onData(NULL, NULL);
36+
_client->onTimeout([](void *r, AsyncClient* c, uint32_t time){ ((AsyncEventSourceClient*)(r))->_onTimeout(time); }, this);
37+
_client->onDisconnect([](void *r, AsyncClient* c){ ((AsyncEventSourceClient*)(r))->_onDisconnect(); }, this);
38+
_server->_addClient(this);
39+
delete request;
40+
}
41+
42+
AsyncEventSourceClient::~AsyncEventSourceClient(){
43+
close();
44+
}
45+
46+
//void AsyncEventSourceClient::_onAck(size_t len, uint32_t time){}
47+
//void AsyncEventSourceClient::_onPoll(){}
48+
//void AsyncEventSourceClient::_onError(int8_t){}
49+
void AsyncEventSourceClient::_onTimeout(uint32_t time){
50+
_client->close(true);
51+
}
52+
53+
void AsyncEventSourceClient::_onDisconnect(){
54+
AsyncClient* cl = _client;
55+
_client = NULL;
56+
cl->free();
57+
delete cl;
58+
_server->_handleDisconnect(this);
59+
}
60+
61+
void AsyncEventSourceClient::close(){
62+
if(_client != NULL)
63+
_client->close(true);
64+
}
65+
66+
void AsyncEventSourceClient::send(const char * message, size_t len){
67+
if(!_client->canSend()){
68+
return;
69+
}
70+
if(_client->space() < len){
71+
return;
72+
}
73+
_client->write(message, len);
74+
}
75+
76+
77+
// Handler
78+
79+
AsyncEventSource::AsyncEventSource(String url):_url(url){}
80+
81+
AsyncEventSource::~AsyncEventSource(){
82+
close();
83+
}
84+
85+
void AsyncEventSource::_addClient(AsyncEventSourceClient * client){
86+
if(_clients == NULL){
87+
_clients = client;
88+
return;
89+
}
90+
AsyncEventSourceClient * c = _clients;
91+
while(c->next != NULL) c = c->next;
92+
c->next = client;
93+
}
94+
95+
void AsyncEventSource::_handleDisconnect(AsyncEventSourceClient * client){
96+
if(_clients == NULL){
97+
return;
98+
}
99+
if(_clients == client){
100+
_clients = client->next;
101+
delete client;
102+
return;
103+
}
104+
AsyncEventSourceClient * c = _clients;
105+
while(c->next != NULL && c->next != client) c = c->next;
106+
if(c->next == NULL){
107+
return;
108+
}
109+
c->next = client->next;
110+
delete client;
111+
}
112+
113+
void AsyncEventSource::close(){
114+
AsyncEventSourceClient * c = _clients;
115+
while(c != NULL){
116+
if(c->connected())
117+
c->close();
118+
c = c->next;
119+
}
120+
}
121+
122+
void AsyncEventSource::send(const char *message, const char *event, uint32_t id, uint32_t reconnect){
123+
if(_clients == NULL)
124+
return;
125+
126+
String ev = "";
127+
128+
if(reconnect){
129+
ev += "retry: ";
130+
ev += String(reconnect);
131+
ev += "\r\n";
132+
}
133+
134+
if(id){
135+
ev += "id: ";
136+
ev += String(reconnect);
137+
ev += "\r\n";
138+
}
139+
140+
if(event != NULL){
141+
ev += "event: ";
142+
ev += String(event);
143+
ev += "\r\n";
144+
}
145+
146+
if(message != NULL){
147+
size_t messageLen = strlen(message);
148+
char * lineStart = (char *)message;
149+
char * lineEnd;
150+
do {
151+
char * nextN = strchr(lineStart, '\n');
152+
char * nextR = strchr(lineStart, '\r');
153+
if(nextN == NULL && nextR == NULL){
154+
//last line
155+
size_t llen = ((char *)message + messageLen) - lineStart;
156+
char * ldata = (char *)malloc(llen+1);
157+
if(ldata != NULL){
158+
memcpy(ldata, lineStart, llen);
159+
ldata[llen] = 0;
160+
ev += "data: ";
161+
ev += ldata;
162+
ev += "\r\n\r\n";
163+
free(ldata);
164+
}
165+
lineStart = (char *)message + messageLen;
166+
} else {
167+
char * nextLine = NULL;
168+
if(nextN != NULL && nextR != NULL){
169+
if(nextR < nextN){
170+
lineEnd = nextR;
171+
if(nextN == (nextR + 1))
172+
nextLine = nextN + 1;
173+
else
174+
nextLine = nextR + 1;
175+
} else {
176+
lineEnd = nextN;
177+
if(nextR == (nextN + 1))
178+
nextLine = nextR + 1;
179+
else
180+
nextLine = nextN + 1;
181+
}
182+
} else if(nextN != NULL){
183+
lineEnd = nextN;
184+
nextLine = nextN + 1;
185+
} else {
186+
lineEnd = nextR;
187+
nextLine = nextR + 1;
188+
}
189+
190+
size_t llen = lineEnd - lineStart;
191+
char * ldata = (char *)malloc(llen+1);
192+
if(ldata != NULL){
193+
memcpy(ldata, lineStart, llen);
194+
ldata[llen] = 0;
195+
ev += "data: ";
196+
ev += ldata;
197+
ev += "\r\n";
198+
free(ldata);
199+
}
200+
lineStart = nextLine;
201+
if(lineStart == ((char *)message + messageLen))
202+
ev += "\r\n";
203+
}
204+
} while(lineStart < ((char *)message + messageLen));
205+
}
206+
207+
//os_printf("EVENT_SOURCE:\n%s", ev.c_str());
208+
209+
AsyncEventSourceClient * c = _clients;
210+
while(c != NULL){
211+
if(c->connected())
212+
c->send(ev.c_str(), ev.length());
213+
c = c->next;
214+
}
215+
ev = String();
216+
}
217+
218+
bool AsyncEventSource::canHandle(AsyncWebServerRequest *request){
219+
if(request->method() != HTTP_GET || !request->url().equals(_url))
220+
return false;
221+
return true;
222+
}
223+
224+
void AsyncEventSource::handleRequest(AsyncWebServerRequest *request){
225+
request->send(new AsyncEventSourceResponse(this));
226+
}
227+
228+
// Response
229+
230+
AsyncEventSourceResponse::AsyncEventSourceResponse(AsyncEventSource *server){
231+
_server = server;
232+
_code = 200;
233+
_contentType = "text/event-stream";
234+
_sendContentLength = false;
235+
addHeader("Cache-Control", "no-cache");
236+
addHeader("Connection","keep-alive");
237+
}
238+
239+
void AsyncEventSourceResponse::_respond(AsyncWebServerRequest *request){
240+
String out = _assembleHead(request->version());
241+
request->client()->write(out.c_str(), _headLength);
242+
_state = RESPONSE_WAIT_ACK;
243+
}
244+
245+
size_t AsyncEventSourceResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time){
246+
if(len){
247+
new AsyncEventSourceClient(request, _server);
248+
}
249+
return 0;
250+
}

0 commit comments

Comments
 (0)