Merge branch 'master' into fix-serveStatic

Conflicts:
	src/AsyncWebServerHandlerImpl.h
This commit is contained in:
Andrew Melvin
2016-01-27 22:01:05 +02:00
13 changed files with 717 additions and 41 deletions

View File

@@ -1,2 +1,10 @@
# ESPAsyncWebServer # ESPAsyncWebServer
[![Join the chat at https://gitter.im/me-no-dev/ESPAsyncWebServer](https://badges.gitter.im/me-no-dev/ESPAsyncWebServer.svg)](https://gitter.im/me-no-dev/ESPAsyncWebServer?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
Async Web Server for ESP8266 Arduino Async Web Server for ESP8266 Arduino
Requires [ESPAsyncTCP](https://github.com/me-no-dev/ESPAsyncTCP) to work
This library should be considered experimental
Examples, description and all will be provided really soon :)
To use this library you need to have the git versions of either ESP8266 or ESP31B Arduino Core

View File

@@ -0,0 +1,199 @@
#include <ESP31BWiFi.h>
#include <ESPAsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <FS.h>
// WEB HANDLER IMPLEMENTATION
class SPIFFSEditor: public AsyncWebHandler {
private:
String _username;
String _password;
bool _uploadAuthenticated;
public:
SPIFFSEditor(String username=String(), String password=String()):_username(username),_password(password),_uploadAuthenticated(false){}
bool canHandle(AsyncWebServerRequest *request){
if(request->method() == HTTP_GET && request->url() == "/edit" && (SPIFFS.exists("/edit.htm") || SPIFFS.exists("/edit.htm.gz")))
return true;
else if(request->method() == HTTP_GET && request->url() == "/list")
return true;
else if(request->method() == HTTP_GET && (request->url().endsWith("/") || SPIFFS.exists(request->url()) || (!request->hasParam("download") && SPIFFS.exists(request->url()+".gz"))))
return true;
else if(request->method() == HTTP_POST && request->url() == "/edit")
return true;
else if(request->method() == HTTP_DELETE && request->url() == "/edit")
return true;
else if(request->method() == HTTP_PUT && request->url() == "/edit")
return true;
return false;
}
void handleRequest(AsyncWebServerRequest *request){
if(_username.length() && (request->method() != HTTP_GET || request->url() == "/edit" || request->url() == "/list") && !request->authenticate(_username.c_str(),_password.c_str()))
return request->requestAuthentication();
if(request->method() == HTTP_GET && request->url() == "/edit"){
request->send(SPIFFS, "/edit.htm");
} else if(request->method() == HTTP_GET && request->url() == "/list"){
if(request->hasParam("dir")){
String path = request->getParam("dir")->value();
Dir dir = SPIFFS.openDir(path);
path = String();
String output = "[";
while(dir.next()){
File entry = dir.openFile("r");
if (output != "[") output += ',';
bool isDir = false;
output += "{\"type\":\"";
output += (isDir)?"dir":"file";
output += "\",\"name\":\"";
output += String(entry.name()).substring(1);
output += "\"}";
entry.close();
}
output += "]";
request->send(200, "text/json", output);
output = String();
}
else
request->send(400);
} else if(request->method() == HTTP_GET){
String path = request->url();
if(path.endsWith("/"))
path += "index.htm";
request->send(SPIFFS, path, String(), request->hasParam("download"));
} else if(request->method() == HTTP_DELETE){
if(request->hasParam("path", true)){
SPIFFS.remove(request->getParam("path", true)->value());
request->send(200, "", "DELETE: "+request->getParam("path", true)->value());
} else
request->send(404);
} else if(request->method() == HTTP_POST){
if(request->hasParam("data", true, true) && SPIFFS.exists(request->getParam("data", true, true)->value()))
request->send(200, "", "UPLOADED: "+request->getParam("data", true, true)->value());
else
request->send(500);
} else if(request->method() == HTTP_PUT){
if(request->hasParam("path", true)){
String filename = request->getParam("path", true)->value();
if(SPIFFS.exists(filename)){
request->send(200);
} else {
File f = SPIFFS.open(filename, "w");
if(f){
f.write(0x00);
f.close();
request->send(200, "", "CREATE: "+filename);
} else {
request->send(500);
}
}
} else
request->send(400);
}
}
void handleUpload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final){
if(!index){
if(!_username.length() || request->authenticate(_username.c_str(),_password.c_str()))
_uploadAuthenticated = true;
request->_tempFile = SPIFFS.open(filename, "w");
}
if(_uploadAuthenticated && request->_tempFile && len){
request->_tempFile.write(data,len);
}
if(_uploadAuthenticated && final)
if(request->_tempFile) request->_tempFile.close();
}
};
// SKETCH BEGIN
AsyncWebServer server(80);
const char* ssid = "**********";
const char* password = "************";
const char* http_username = "admin";
const char* http_password = "admin";
void setup(){
Serial.begin(115200);
Serial.setDebugOutput(true);
SPIFFS.begin();
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
if(WiFi.waitForConnectResult() != WL_CONNECTED){
Serial.printf("WiFi Failed!\n");
}
server.serveStatic("/fs", SPIFFS, "/");
server.on("/heap", HTTP_GET, [](AsyncWebServerRequest *request){
request->send(200, "text/plain", String(ESP.getFreeHeap()));
});
server.addHandler(new SPIFFSEditor(http_username,http_password));
server.onNotFound([](AsyncWebServerRequest *request){
os_printf("NOT_FOUND: ");
if(request->method() == HTTP_GET)
os_printf("GET");
else if(request->method() == HTTP_POST)
os_printf("POST");
else if(request->method() == HTTP_DELETE)
os_printf("DELETE");
else if(request->method() == HTTP_PUT)
os_printf("PUT");
else if(request->method() == HTTP_PATCH)
os_printf("PATCH");
else if(request->method() == HTTP_HEAD)
os_printf("HEAD");
else if(request->method() == HTTP_OPTIONS)
os_printf("OPTIONS");
else
os_printf("UNKNOWN");
os_printf(" http://%s%s\n", request->host().c_str(), request->url().c_str());
if(request->contentLength()){
os_printf("_CONTENT_TYPE: %s\n", request->contentType().c_str());
os_printf("_CONTENT_LENGTH: %u\n", request->contentLength());
}
int headers = request->headers();
int i;
for(i=0;i<headers;i++){
AsyncWebHeader* h = request->getHeader(i);
os_printf("_HEADER[%s]: %s\n", h->name().c_str(), h->value().c_str());
}
int params = request->params();
for(i=0;i<params;i++){
AsyncWebParameter* p = request->getParam(i);
if(p->isFile()){
os_printf("_FILE[%s]: %s, size: %u\n", p->name().c_str(), p->value().c_str(), p->size());
} else if(p->isPost()){
os_printf("_POST[%s]: %s\n", p->name().c_str(), p->value().c_str());
} else {
os_printf("_GET[%s]: %s\n", p->name().c_str(), p->value().c_str());
}
}
request->send(404);
});
server.onFileUpload([](AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final){
if(!index)
os_printf("UploadStart: %s\n", filename.c_str());
os_printf("%s", (const char*)data);
if(final)
os_printf("UploadEnd: %s (%u)\n", filename.c_str(), index+len);
});
server.onRequestBody([](AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total){
if(!index)
os_printf("BodyStart: %u\n", total);
os_printf("%s", (const char*)data);
if(index + len == total)
os_printf("BodyEnd: %u\n", total);
});
server.begin();
}
void loop(){}

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

View File

@@ -0,0 +1,366 @@
<!--
FSWebServer - Example Index Page
Copyright (c) 2015 Hristo Gochkov. All rights reserved.
This file is part of the ESP8266WebServer library for Arduino environment.
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
-->
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<title>ESP Monitor</title>
<script type="text/javascript" src="graphs.js"></script>
<script type="text/javascript">
if (typeof XMLHttpRequest === "undefined") {
XMLHttpRequest = function () {
try { return new ActiveXObject("Msxml2.XMLHTTP.6.0"); } catch (e) {}
try { return new ActiveXObject("Msxml2.XMLHTTP.3.0"); } catch (e) {}
try { return new ActiveXObject("Microsoft.XMLHTTP"); } catch (e) {}
throw new Error("This browser does not support XMLHttpRequest.");
};
}
var QueuedRequester = function () {
this.queue = [];
this.running = false;
this.xmlhttp = null;
}
QueuedRequester.prototype = {
_request: function(req){
this.running = true;
if(!req instanceof Object) return;
var that = this;
function scriptCb(d){ return function(e){
d.callback(e);
if(that.queue.length === 0) that.running = false;
if(that.running) that._request(that.queue.shift());
}}
function ajaxCb(x,d){ return function(){
if (x.readyState == 4){
if(x.status == 200){
if(d.type === "json") d.callback(JSON.parse(x.responseText));
else if(d.type === "xml") d.callback(x.responseXML);
else d.callback(x.responseText);
}
if(that.queue.length === 0) that.running = false;
if(that.running) that._request(that.queue.shift());
}
}}
var p = "";
if(req.params instanceof Object){
for (var key in req.params) {
if(p === "")
p += (req.method === "GET" || req.method === "SCRIPT")?"?":"";
else
p += "&";
p += key+"="+req.params[key];
};
}
if(req.method === "SCRIPT"){
var head = document.getElementsByTagName('head')[0];
if(req.type === "css"){
var c = document.createElement('link');
c.setAttribute('href', req.url+p);
c.setAttribute('rel', 'stylesheet');
c.setAttribute('type', 'text/css');
c.setAttribute('async', 'true');
c.addEventListener('load', scriptCb(req), false);
head.appendChild(c);
} else if(req.type === "javascript"){
var s = document.createElement('script');
s.setAttribute('src', req.url+p);
s.setAttribute('type', 'text/javascript');
s.setAttribute('async', 'true');
s.addEventListener('load', scriptCb(req), false);
head.appendChild(s);
}
return;
}
this.xmlhttp = new XMLHttpRequest();
this.xmlhttp.onreadystatechange = ajaxCb(this.xmlhttp, req);
if(req.method === "GET"){
this.xmlhttp.open(req.method, req.url+p, true);
this.xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
this.xmlhttp.send();
} else {
this.xmlhttp.open(req.method, req.url, true);
this.xmlhttp.send(p);
}
},
stop: function(){
if(this.running) this.running = false;
if(this.xmlhttp && this.xmlhttp.readyState < 4){
this.xmlhttp.abort();
}
},
add: function(url, method, params, type, callback){
this.queue.push({url:url,method:method,params:params,type:type,callback:callback});
if(!this.running){
this._request(this.queue.shift());
}
}
}
var requests = new QueuedRequester();
var heap,temp,digi,rssi;
var reloadPeriod = 1000;
var running = false;
function ipStr(ip){
return (ip & 0xFF) + "." + ((ip >> 8) & 0xFF) + "." + ((ip >> 16) & 0xFF) + "." + ((ip >> 24) & 0xFF);
}
function statusStr(status){
if(status === 0) return "IDLE";
else if(status === 1) return "CONNECTING";
else if(status === 2) return "WRONG_PASSWORD";
else if(status === 3) return "NO_AP_FOUND";
else if(status === 4) return "CONNECT_FAILED";
else if(status === 5) return "CONNECTED";
else return "UNKNOWN";
}
function securityStr(status){
if(status === 7) return "";
else if(status === 8) return "WPA/WPA2";
else if(status === 2) return "WPA";
else if(status === 4) return "WPA2";
else if(status === 5) return "WEP";
else return "UNKNOWN";
}
function loadValues(){
if(!running) return;
requests.add("/wificonf", "GET", null, "json", function(res){
if(res){
wifiState(res);
//rssi.add(res.rssi);
//heap.add(res.heap);
//temp.add(res.analog);
//digi.add(res.gpio);
if(running) setTimeout(loadValues, reloadPeriod);
} else running = false;
});
};
function loadAvailables(){
if(!running) return;
requests.add("/scan", "GET", null, "json", function(res){
if(res){
wifiScan(res);
if(running) setTimeout(loadAvailables, 10*1000);
} else running = false;
});
};
/*
var avrParts = null;
var signature = null;
var lfuse = null;
var hfuse = null;
var efuse = null;
var lock = null;
var part = null;
function onNoPart(){
document.getElementById("avr-info").innerText = "AVR Part not found";
}
function onPart(){
document.getElementById("avr-info").innerText = ("Part: id:"+part.id+" name:"+part.name+", signature:"+part.signature+", flash:"+part.flash+"B, eeprom:"+part.eeprom+"B, low:"+((part.fuses.low)?lfuse:"")+", high:"+((part.fuses.high)?hfuse:"")+", ext:"+((part.fuses.ext)?efuse:"")+", lock:"+((part.fuses.lock)?lock:"")+"");
}
function avrInfo(){
requests.add("/avr/info", "GET", null, "json", function(res){
var sig = (res.sig0 << 16) + (res.sig1 << 8) + res.sig2;
if(sig == 0){
onNoPart();
return;
}
signature = "0x"+sig.toString(16);
lfuse = res.low.toString(16);
hfuse = res.high.toString(16);
efuse = res.ext.toString(16);
lock = res.lock.toString(16);
if(avrParts == null){
requests.add("/avrs.json", "GET", null, "json", function(res){
if(!res) return;
avrParts = res;
for(p in avrParts){
if(avrParts[p].signature === signature){
part = avrParts[p];
onPart();
}
}
});
} else {
for(p in avrParts){
if(avrParts[p].signature === signature){
part = avrParts[p];
onPart();
}
}
}
});
};
*/
function run(){
if(!running){
running = true;
loadValues();
loadAvailables();
}
}
function onBodyLoad(){
var refreshInput = document.getElementById("refresh-rate");
refreshInput.value = reloadPeriod;
refreshInput.onchange = function(e){
var value = parseInt(e.target.value);
reloadPeriod = (value > 0)?value:0;
e.target.value = reloadPeriod;
}
var stopButton = document.getElementById("stop-button");
stopButton.onclick = function(e){
running = false;
requests.stop();
}
var startButton = document.getElementById("start-button");
startButton.onclick = function(e){
run();
}
// Example with 10K thermistor
//function calcThermistor(v) {
// var t = Math.log(((10230000 / v) - 10000));
// t = (1/(0.001129148+(0.000234125*t)+(0.0000000876741*t*t*t)))-273.15;
// return (t>120)?0:Math.round(t*10)/10;
//}
//temp = createGraph(document.getElementById("analog"), "Temperature", 100, 128, 10, 40, false, "cyan", calcThermistor);
//temp = createGraph(document.getElementById("analog"), "Analog Input", 100, 128, 0, 1023, false, "cyan");
rssi = createGraph(document.getElementById("rssi"), "WiFi RSSI", 100, 116, -100, -10, false, "green");
//heap = createGraph(document.getElementById("heap"), "Current Heap", 100, 125, 0, 40000, true, "orange");
//digi = createDigiGraph(document.getElementById("digital"), "GPIO", 100, 146, [0, 4, 5, 16], "gold");
//run();
//avrInfo();
}
function wifiState(res){
if(!res) return;
document.getElementsByName("ssid")[0].value = res.sta.ssid;
document.getElementsByName("bssid")[0].value = res.sta.bssid;
document.getElementsByName("channel")[0].value = res.sta.channel;
rssi.add(res.sta.rssi);
document.getElementsByName("status")[0].value = statusStr(res.sta.status);
document.getElementsByName("rssi")[0].value = res.sta.rssi;
document.getElementsByName("mac")[0].value = res.sta.mac;
document.getElementsByName("ip")[0].value = ipStr(res.sta.ip);
document.getElementsByName("mask")[0].value = ipStr(res.sta.netmask);
document.getElementsByName("gateway")[0].value = ipStr(res.sta.gateway);
document.getElementsByName("dns")[0].value = ipStr(res.sta.dns);
}
function wifiScan(res){
if(!res || !res.length) return;
var table = document.getElementById("available");
table.innerHTML = "";
for (var i=0; i<res.length; i++){
var row = document.createElement("tr");
row.innerHTML = "<td>"+(i+1)+"</td><td>"+res[i].ssid+"</td><td>"+res[i].bssid+"</td><td>"+res[i].channel+"</td><td>"+securityStr(res[i].secure)+"</td><td>"+res[i].hidden+"</td><td>"+res[i].rssi+"</td>";
table.appendChild(row);
}
}
</script>
<style>
#wifi-state {
display: block;
border: 1px solid rgb(68, 68, 68);
padding: 5px;
margin: 5px;
width: 500px;
background-color: rgb(238, 238, 238);
}
#wifi-state label {
width:100px;
display:inline-block;
font:14px Verdana;
}
#wifi-state input {
margin:1px;
}
table {
border: 1px solid rgb(68, 68, 68);
padding: 5px;
margin: 5px;
}
th {
background-color:#CCCCCC;
}
</style>
</head>
<body id="index" style="margin:0; padding:0;" onload="onBodyLoad()">
<!--div id="avr-info"></div-->
<div>
<div>
<div id="controls" style="display: block; border: 1px solid rgb(68, 68, 68); padding: 5px; margin: 5px; width: 362px; background-color: rgb(238, 238, 238);">
<label>Period (ms):</label>
<input type="number" id="refresh-rate"/>
<input type="button" id="start-button" value="Start"/>
<input type="button" id="stop-button" value="Stop"/>
</div>
<div id="rssi"></div>
</div>
<div style="display:inline-block">
<div id="wifi-form">
<form id="wifi-state">
<label>SSID:</label><input type="text" name="ssid" disabled="true"/><label>MAC:</label><input type="text" name="mac" disabled="true"/><br />
<label>BSSID:</label><input type="text" name="bssid" disabled="true"/><label>IP:</label><input type="text" name="ip" disabled="true"/><br />
<label>Channel:</label><input type="text" name="channel" disabled="true"/><label>Netmask:</label><input type="text" name="mask" disabled="true"/><br />
<label>Status:</label><input type="text" name="status" disabled="true"/><label>Gateway:</label><input type="text" name="gateway" disabled="true"/><br />
<label>RSSI:</label><input type="text" name="rssi" disabled="true"/><label>DNS:</label><input type="text" name="dns" disabled="true"/>
</form>
</div>
</div>
</div>
<div>
<table>
<thead>
<tr>
<th>&nbsp;&nbsp;&nbsp;</th>
<th style="width:200px">SSID</th>
<th style="width:150px">BSSID</th>
<th>Channel</th>
<th style="width:80px">Secure</th>
<th>Hidden</th>
<th>RSSI</th>
</tr>
</thead>
<tbody id="available"></tbody>
</table>
</div>
<!--div id="heap"></div>
<div id="analog"></div>
<div id="digital"></div-->
</body>
</html>

View File

@@ -1,9 +1,9 @@
name=ESP8266 Async Web Server name=ESP Async WebServer
version=1.0.0 version=1.0.0
author=ESP8266 author=Me-No-Dev
maintainer=Me-No-Dev maintainer=Me-No-Dev
sentence=Async Web Server for ESP8266 sentence=Async Web Server for ESP8266 and ESP31B
paragraph=Async Web Server for ESP8266 paragraph=Async Web Server for ESP8266 and ESP31B
category=Other category=Other
url=https://github.com/me-no-dev/ESPAsyncWebServer url=https://github.com/me-no-dev/ESPAsyncWebServer
architectures=* architectures=*

View File

@@ -21,7 +21,8 @@ class AsyncStaticWebHandler: public AsyncWebHandler {
public: public:
AsyncStaticWebHandler(FS& fs, const char* path, const char* uri, const char* cache_header) AsyncStaticWebHandler(FS& fs, const char* path, const char* uri, const char* cache_header)
: _fs(fs), _uri(uri), _path(path), _cache_header(cache_header){ : _fs(fs), _uri(uri), _path(path), _cache_header(cache_header){
_isFile = _fs.exists(path);
_isFile = _fs.exists(path) || _fs.exists((String(path)+".gz").c_str());
if (_uri != "/" && _uri.endsWith("/")) { if (_uri != "/" && _uri.endsWith("/")) {
_uri = _uri.substring(0, _uri.length() - 1); _uri = _uri.substring(0, _uri.length() - 1);
//os_printf("[AsyncStaticWebHandler] _uri / removed"); //os_printf("[AsyncStaticWebHandler] _uri / removed");

View File

@@ -57,6 +57,17 @@ class AsyncCallbackResponse: public AsyncAbstractResponse {
size_t _fillBuffer(uint8_t *buf, size_t maxLen); size_t _fillBuffer(uint8_t *buf, size_t maxLen);
}; };
class AsyncChunkedResponse: public AsyncAbstractResponse {
private:
AwsResponseFiller _content;
public:
AsyncChunkedResponse(String contentType, AwsResponseFiller callback);
bool _sourceValid(){ return !!(_content); }
size_t _fillBuffer(uint8_t *buf, size_t maxLen);
};
class cbuf;
class AsyncResponseStream: public AsyncAbstractResponse, public Print { class AsyncResponseStream: public AsyncAbstractResponse, public Print {
private: private:
cbuf *_content; cbuf *_content;

View File

@@ -86,6 +86,7 @@ class AsyncWebServerRequest {
String _temp; String _temp;
uint8_t _parseState; uint8_t _parseState;
uint8_t _version;
WebRequestMethod _method; WebRequestMethod _method;
String _url; String _url;
String _host; String _host;
@@ -142,6 +143,7 @@ class AsyncWebServerRequest {
~AsyncWebServerRequest(); ~AsyncWebServerRequest();
AsyncClient* client(){ return _client; } AsyncClient* client(){ return _client; }
uint8_t version(){ return _version; }
WebRequestMethod method(){ return _method; } WebRequestMethod method(){ return _method; }
String url(){ return _url; } String url(){ return _url; }
String host(){ return _host; } String host(){ return _host; }
@@ -161,11 +163,13 @@ class AsyncWebServerRequest {
void send(FS &fs, String path, String contentType=String(), bool download=false); void send(FS &fs, String path, String contentType=String(), bool download=false);
void send(Stream &stream, String contentType, size_t len); void send(Stream &stream, String contentType, size_t len);
void send(String contentType, size_t len, AwsResponseFiller callback); void send(String contentType, size_t len, AwsResponseFiller callback);
void sendChunked(String contentType, AwsResponseFiller callback);
AsyncWebServerResponse *beginResponse(int code, String contentType=String(), String content=String()); AsyncWebServerResponse *beginResponse(int code, String contentType=String(), String content=String());
AsyncWebServerResponse *beginResponse(FS &fs, String path, String contentType=String(), bool download=false); AsyncWebServerResponse *beginResponse(FS &fs, String path, String contentType=String(), bool download=false);
AsyncWebServerResponse *beginResponse(Stream &stream, String contentType, size_t len); AsyncWebServerResponse *beginResponse(Stream &stream, String contentType, size_t len);
AsyncWebServerResponse *beginResponse(String contentType, size_t len, AwsResponseFiller callback); AsyncWebServerResponse *beginResponse(String contentType, size_t len, AwsResponseFiller callback);
AsyncWebServerResponse *beginChunkedResponse(String contentType, AwsResponseFiller callback);
AsyncResponseStream *beginResponseStream(String contentType, size_t len, size_t bufferSize=1460); AsyncResponseStream *beginResponseStream(String contentType, size_t len, size_t bufferSize=1460);
int headers(); // get header count int headers(); // get header count
@@ -221,6 +225,8 @@ class AsyncWebServerResponse {
AsyncWebHeader *_headers; AsyncWebHeader *_headers;
String _contentType; String _contentType;
size_t _contentLength; size_t _contentLength;
bool _sendContentLength;
bool _chunked;
size_t _headLength; size_t _headLength;
size_t _sentLength; size_t _sentLength;
size_t _ackedLength; size_t _ackedLength;
@@ -230,8 +236,10 @@ class AsyncWebServerResponse {
public: public:
AsyncWebServerResponse(); AsyncWebServerResponse();
virtual ~AsyncWebServerResponse(); virtual ~AsyncWebServerResponse();
virtual void setContentLength(size_t len);
virtual void setContentType(String type);
virtual void addHeader(String name, String value); virtual void addHeader(String name, String value);
virtual String _assembleHead(); virtual String _assembleHead(uint8_t version);
virtual bool _finished(); virtual bool _finished();
virtual bool _failed(); virtual bool _failed();
virtual void _respond(AsyncWebServerRequest *request); virtual void _respond(AsyncWebServerRequest *request);

View File

@@ -9,7 +9,7 @@
#define STRINGARRAY_H_ #define STRINGARRAY_H_
#include "stddef.h" #include "stddef.h"
#include "String.h" #include "WString.h"
class StringArrayItem; class StringArrayItem;
class StringArrayItem { class StringArrayItem {

View File

@@ -2,7 +2,6 @@
#include "AsyncWebServerResponseImpl.h" #include "AsyncWebServerResponseImpl.h"
#include "cbuf.h" #include "cbuf.h"
/* /*
* Abstract Response * Abstract Response
* */ * */
@@ -53,14 +52,16 @@ const char* AsyncWebServerResponse::_responseCodeToString(int code) {
} }
AsyncWebServerResponse::AsyncWebServerResponse() AsyncWebServerResponse::AsyncWebServerResponse()
:_code(0) : _code(0)
,_headers(NULL) , _headers(NULL)
,_contentType() , _contentType()
,_contentLength(0) , _contentLength(0)
,_headLength(0) , _sendContentLength(true)
,_sentLength(0) , _chunked(false)
,_ackedLength(0) , _headLength(0)
,_state(RESPONSE_SETUP) , _sentLength(0)
, _ackedLength(0)
, _state(RESPONSE_SETUP)
{ {
addHeader("Connection","close"); addHeader("Connection","close");
addHeader("Access-Control-Allow-Origin","*"); addHeader("Access-Control-Allow-Origin","*");
@@ -74,6 +75,16 @@ AsyncWebServerResponse::~AsyncWebServerResponse(){
} }
} }
void AsyncWebServerResponse::setContentLength(size_t len){
if(_state == RESPONSE_SETUP)
_contentLength = len;
}
void AsyncWebServerResponse::setContentType(String type){
if(_state == RESPONSE_SETUP)
_contentType = type;
}
void AsyncWebServerResponse::addHeader(String name, String value){ void AsyncWebServerResponse::addHeader(String name, String value){
AsyncWebHeader *header = new AsyncWebHeader(name, value); AsyncWebHeader *header = new AsyncWebHeader(name, value);
if(_headers == NULL){ if(_headers == NULL){
@@ -85,12 +96,19 @@ void AsyncWebServerResponse::addHeader(String name, String value){
} }
} }
String AsyncWebServerResponse::_assembleHead(){ String AsyncWebServerResponse::_assembleHead(uint8_t version){
String out = "HTTP/1.1 " + String(_code) + " " + _responseCodeToString(_code) + "\r\n"; if(version){
out += "Content-Length: " + String(_contentLength) + "\r\n"; addHeader("Accept-Ranges","none");
if(_contentType.length()){ if(_chunked)
out += "Content-Type: " + _contentType + "\r\n"; addHeader("Transfer-Encoding","chunked");
} }
String out = "HTTP/1." + String(version) + " " + String(_code) + " " + _responseCodeToString(_code) + "\r\n";
if(_sendContentLength)
out += "Content-Length: " + String(_contentLength) + "\r\n";
if(_contentType.length())
out += "Content-Type: " + _contentType + "\r\n";
AsyncWebHeader *h; AsyncWebHeader *h;
while(_headers != NULL){ while(_headers != NULL){
h = _headers; h = _headers;
@@ -124,7 +142,7 @@ AsyncBasicResponse::AsyncBasicResponse(int code, String contentType, String cont
void AsyncBasicResponse::_respond(AsyncWebServerRequest *request){ void AsyncBasicResponse::_respond(AsyncWebServerRequest *request){
_state = RESPONSE_HEADERS; _state = RESPONSE_HEADERS;
String out = _assembleHead(); String out = _assembleHead(request->version());
size_t outLen = out.length(); size_t outLen = out.length();
size_t space = request->client()->space(); size_t space = request->client()->space();
if(!_contentLength && space >= outLen){ if(!_contentLength && space >= outLen){
@@ -193,7 +211,7 @@ void AsyncAbstractResponse::_respond(AsyncWebServerRequest *request){
request->send(500); request->send(500);
return; return;
} }
_head = _assembleHead(); _head = _assembleHead(request->version());
_state = RESPONSE_HEADERS; _state = RESPONSE_HEADERS;
size_t outLen = _head.length(); size_t outLen = _head.length();
size_t space = request->client()->space(); size_t space = request->client()->space();
@@ -218,18 +236,42 @@ size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest *request, size_t len, u
_ackedLength += len; _ackedLength += len;
size_t space = request->client()->space(); size_t space = request->client()->space();
if(_state == RESPONSE_CONTENT){ if(_state == RESPONSE_CONTENT){
size_t remaining = _contentLength - _sentLength; size_t outLen;
size_t outLen = (remaining > space)?space:remaining; size_t readLen = 0;
if(_chunked || !_sendContentLength){
outLen = space;
} else {
size_t remaining = _contentLength - _sentLength;
outLen = (remaining > space)?space:remaining;
}
uint8_t *buf = (uint8_t *)malloc(outLen); uint8_t *buf = (uint8_t *)malloc(outLen);
outLen = _fillBuffer(buf, outLen);
if(_chunked){
readLen = _fillBuffer(buf, outLen - 8);
char pre[6];
sprintf(pre, "%x\r\n", readLen);
size_t preLen = strlen(pre);
memmove(buf+preLen, buf, preLen);
for(size_t i=0; i<preLen; i++)
buf[i] = pre[i];
outLen = preLen + readLen;
buf[outLen++] = '\r';
buf[outLen++] = '\n';
} else {
outLen = _fillBuffer(buf, outLen);
}
if(outLen) if(outLen)
request->client()->write((const char*)buf, outLen); outLen = request->client()->write((const char*)buf, outLen);
_sentLength += outLen; _sentLength += outLen;
free(buf); free(buf);
if(_sentLength == _contentLength){
if((_chunked && readLen == 0) || (!_sendContentLength && outLen == 0) || _sentLength == _contentLength){
_state = RESPONSE_WAIT_ACK; _state = RESPONSE_WAIT_ACK;
} }
return outLen; return outLen;
} else if(_state == RESPONSE_HEADERS){ } else if(_state == RESPONSE_HEADERS){
size_t outLen = _head.length(); size_t outLen = _head.length();
if(space >= outLen){ if(space >= outLen){
@@ -244,8 +286,10 @@ size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest *request, size_t len, u
return out.length(); return out.length();
} }
} else if(_state == RESPONSE_WAIT_ACK){ } else if(_state == RESPONSE_WAIT_ACK){
if(_ackedLength >= (_headLength+_contentLength)){ if(!_sendContentLength || _ackedLength >= (_headLength+_contentLength)){
_state = RESPONSE_END; _state = RESPONSE_END;
if(!_chunked && !_sendContentLength)
request->client()->close();
} }
} }
return 0; return 0;
@@ -330,6 +374,8 @@ AsyncCallbackResponse::AsyncCallbackResponse(String contentType, size_t len, Aws
_code = 200; _code = 200;
_content = callback; _content = callback;
_contentLength = len; _contentLength = len;
if(!len)
_sendContentLength = false;
_contentType = contentType; _contentType = contentType;
} }
@@ -337,6 +383,23 @@ size_t AsyncCallbackResponse::_fillBuffer(uint8_t *data, size_t len){
return _content(data, len); return _content(data, len);
} }
/*
* Chunked Response
* */
AsyncChunkedResponse::AsyncChunkedResponse(String contentType, AwsResponseFiller callback){
_code = 200;
_content = callback;
_contentLength = 0;
_contentType = contentType;
_sendContentLength = false;
_chunked = true;
}
size_t AsyncChunkedResponse::_fillBuffer(uint8_t *data, size_t len){
return _content(data, len);
}
/* /*
* Response Stream (You can print/write/printf to it, up to the contentLen bytes) * Response Stream (You can print/write/printf to it, up to the contentLen bytes)
@@ -345,6 +408,8 @@ size_t AsyncCallbackResponse::_fillBuffer(uint8_t *data, size_t len){
AsyncResponseStream::AsyncResponseStream(String contentType, size_t len, size_t bufferSize){ AsyncResponseStream::AsyncResponseStream(String contentType, size_t len, size_t bufferSize){
_code = 200; _code = 200;
_contentLength = len; _contentLength = len;
if(!len)
_sendContentLength = false;
_contentType = contentType; _contentType = contentType;
_content = new cbuf(bufferSize); _content = new cbuf(bufferSize);
} }
@@ -358,16 +423,15 @@ size_t AsyncResponseStream::_fillBuffer(uint8_t *buf, size_t maxLen){
} }
size_t AsyncResponseStream::write(const uint8_t *data, size_t len){ size_t AsyncResponseStream::write(const uint8_t *data, size_t len){
if(_finished()) if(_finished() || (_content->room() == 0 && ETS_INTR_WITHINISR()))
return 0; return 0;
//while(_content->room() < len) delay(1); if(len > _content->available())
len = _content->available();
while(_content->room() < len) delay(0);
return _content->write((const char*)data, len); return _content->write((const char*)data, len);
} }
size_t AsyncResponseStream::write(uint8_t data){ size_t AsyncResponseStream::write(uint8_t data){
if(_finished())
return 0;
//while(_content->room() == 0) delay(1);
return write(&data, 1); return write(&data, 1);
} }

View File

@@ -9,6 +9,10 @@
#include "AsyncWebServerResponseImpl.h" #include "AsyncWebServerResponseImpl.h"
#include <libb64/cencode.h> #include <libb64/cencode.h>
#ifndef ESP8266
#define os_strlen strlen
#endif
#define __is_param_char(c) ((c) && ((c) != '{') && ((c) != '[') && ((c) != '&') && ((c) != '=')) #define __is_param_char(c) ((c) && ((c) != '{') && ((c) != '[') && ((c) != '&') && ((c) != '='))
enum { PARSE_REQ_START, PARSE_REQ_HEADERS, PARSE_REQ_BODY, PARSE_REQ_END, PARSE_REQ_FAIL }; enum { PARSE_REQ_START, PARSE_REQ_HEADERS, PARSE_REQ_BODY, PARSE_REQ_END, PARSE_REQ_FAIL };
@@ -21,6 +25,7 @@ AsyncWebServerRequest::AsyncWebServerRequest(AsyncWebServer* s, AsyncClient* c)
, _interestingHeaders(new StringArray()) , _interestingHeaders(new StringArray())
, _temp() , _temp()
, _parseState(0) , _parseState(0)
, _version(0)
, _method(HTTP_ANY) , _method(HTTP_ANY)
, _url() , _url()
, _host() , _host()
@@ -147,11 +152,12 @@ void AsyncWebServerRequest::_onAck(size_t len, uint32_t time){
} }
void AsyncWebServerRequest::_onError(int8_t error){ void AsyncWebServerRequest::_onError(int8_t error){
//os_printf("e:%d:%u\n", error, _client->state()); if(error != -11)
os_printf("ERROR[%d] %s, state: %s\n", error, _client->errorToString(error), _client->stateToString());
} }
void AsyncWebServerRequest::_onTimeout(uint32_t time){ void AsyncWebServerRequest::_onTimeout(uint32_t time){
//os_printf("t:%u\n", time); os_printf("TIMEOUT: %u, state: %s\n", time, _client->stateToString());
_client->close(); _client->close();
} }
@@ -224,6 +230,9 @@ bool AsyncWebServerRequest::_parseReqHead(){
} }
} }
_temp = _temp.substring(_temp.indexOf(' ')+1);
if(_temp.startsWith("HTTP/1.1"))
_version = 1;
_temp = String(); _temp = String();
return true; return true;
} }
@@ -615,32 +624,42 @@ AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(String contentType
return new AsyncCallbackResponse(contentType, len, callback); return new AsyncCallbackResponse(contentType, len, callback);
} }
AsyncWebServerResponse * AsyncWebServerRequest::beginChunkedResponse(String contentType, AwsResponseFiller callback){
if(_version)
return new AsyncChunkedResponse(contentType, callback);
return new AsyncCallbackResponse(contentType, 0, callback);
}
AsyncResponseStream * AsyncWebServerRequest::beginResponseStream(String contentType, size_t len, size_t bufferSize){ AsyncResponseStream * AsyncWebServerRequest::beginResponseStream(String contentType, size_t len, size_t bufferSize){
return new AsyncResponseStream(contentType, len, bufferSize); return new AsyncResponseStream(contentType, len, bufferSize);
} }
void AsyncWebServerRequest::send(int code, String contentType, String content){ void AsyncWebServerRequest::send(int code, String contentType, String content){
send(new AsyncBasicResponse(code, contentType, content)); send(beginResponse(code, contentType, content));
} }
void AsyncWebServerRequest::send(FS &fs, String path, String contentType, bool download){ void AsyncWebServerRequest::send(FS &fs, String path, String contentType, bool download){
if(fs.exists(path) || (!download && fs.exists(path+".gz"))){ if(fs.exists(path) || (!download && fs.exists(path+".gz"))){
send(new AsyncFileResponse(fs, path, contentType, download)); send(beginResponse(fs, path, contentType, download));
} else send(404); } else send(404);
} }
void AsyncWebServerRequest::send(Stream &stream, String contentType, size_t len){ void AsyncWebServerRequest::send(Stream &stream, String contentType, size_t len){
send(new AsyncStreamResponse(stream, contentType, len)); send(beginResponse(stream, contentType, len));
} }
void AsyncWebServerRequest::send(String contentType, size_t len, AwsResponseFiller callback){ void AsyncWebServerRequest::send(String contentType, size_t len, AwsResponseFiller callback){
send(new AsyncCallbackResponse(contentType, len, callback)); send(beginResponse(contentType, len, callback));
}
void AsyncWebServerRequest::sendChunked(String contentType, AwsResponseFiller callback){
send(beginChunkedResponse(contentType, callback));
} }
bool AsyncWebServerRequest::authenticate(const char * username, const char * password){ bool AsyncWebServerRequest::authenticate(const char * username, const char * password){
if(_authorization.length()){ if(_authorization.length()){
char toencodeLen = strlen(username)+strlen(password)+1; char toencodeLen = os_strlen(username)+os_strlen(password)+1;
char *toencode = new char[toencodeLen]; char *toencode = new char[toencodeLen];
if(toencode == NULL){ if(toencode == NULL){
return false; return false;