Merge remote-tracking branch 'me-no-dev/master'

This commit is contained in:
Hagai Shatz
2016-06-17 20:14:06 +01:00
6 changed files with 251 additions and 188 deletions

View File

@@ -81,10 +81,11 @@ class AsyncWebHeader {
AsyncWebHeader(String name, String value): _name(name), _value(value), next(NULL){}
AsyncWebHeader(String data): _name(), _value(), next(NULL){
if(!data || !data.length() || data.indexOf(':') < 0)
return;
_name = data.substring(0, data.indexOf(':'));
_value = data.substring(data.indexOf(':') + 2);
if(!data) return;
int index = data.indexOf(':');
if (index < 0) return;
_name = data.substring(0, index);
_value = data.substring(index + 2);
}
~AsyncWebHeader(){}
String name(){ return _name; }
@@ -149,7 +150,6 @@ class AsyncWebServerRequest {
bool _parseReqHead();
bool _parseReqHeader();
void _parseLine();
void _parseByte(uint8_t data);
void _parsePlainPostChar(uint8_t data);
void _parseMultipartPostByte(uint8_t data, bool last);
void _addGetParam(String param);
@@ -160,6 +160,7 @@ class AsyncWebServerRequest {
public:
File _tempFile;
void *_tempObject;
AsyncWebServerRequest *next;
AsyncWebServerRequest(AsyncWebServer*, AsyncClient*);
@@ -185,12 +186,14 @@ class AsyncWebServerRequest {
void send(AsyncWebServerResponse *response);
void send(int code, String contentType=String(), String content=String());
void send(FS &fs, String path, String contentType=String(), bool download=false);
void send(File content, String path, String contentType=String(), bool download=false);
void send(Stream &stream, String contentType, size_t len);
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(FS &fs, String path, String contentType=String(), bool download=false);
AsyncWebServerResponse *beginResponse(File content, String path, String contentType=String(), bool download=false);
AsyncWebServerResponse *beginResponse(Stream &stream, String contentType, size_t len);
AsyncWebServerResponse *beginResponse(String contentType, size_t len, AwsResponseFiller callback);
AsyncWebServerResponse *beginChunkedResponse(String contentType, AwsResponseFiller callback);

View File

@@ -24,47 +24,24 @@
#include "stddef.h"
class RedirectWebHandler: public AsyncWebHandler {
protected:
String _url;
String _location;
uint32_t _exclude_ip;
public:
RedirectWebHandler(const char* url, const char* location, uint32_t exclude_ip)
: _url(url), _location(location) { _exclude_ip = exclude_ip; }
bool canHandle(AsyncWebServerRequest *request);
void handleRequest(AsyncWebServerRequest *request);
};
class AsyncStaticWebHandler: public AsyncWebHandler {
private:
String _getPath(AsyncWebServerRequest *request);
bool _getFile(AsyncWebServerRequest *request);
bool _fileExists(AsyncWebServerRequest *request, const String path);
uint8_t _countBits(const uint8_t value);
protected:
FS _fs;
String _uri;
String _path;
String _cache_header;
String _modified_header;
bool _isFile;
bool _isDir;
bool _gzipFirst;
uint8_t _gzipStats;
uint8_t _fileStats;
public:
AsyncStaticWebHandler(FS& fs, const char* path, const char* uri, const char* cache_header, const char* modified_header)
: _fs(fs), _uri(uri), _path(path), _cache_header(cache_header), _modified_header(modified_header) {
_isFile = _fs.exists(path) || _fs.exists((String(path)+".gz").c_str());
if (_uri != "/" && _uri.endsWith("/")) {
_uri = _uri.substring(0, _uri.length() - 1);
DEBUGF("[AsyncStaticWebHandler] _uri / removed\n");
}
if (_path != "/" && _path.endsWith("/")) {
_path = _path.substring(0, _path.length() - 1);
DEBUGF("[AsyncStaticWebHandler] _path / removed\n");
}
}
AsyncStaticWebHandler(FS& fs, const char* path, const char* uri, const char* cache_header);
bool canHandle(AsyncWebServerRequest *request);
void handleRequest(AsyncWebServerRequest *request);
};
class AsyncCallbackWebHandler: public AsyncWebHandler {

View File

@@ -21,102 +21,121 @@
#include "ESPAsyncWebServer.h"
#include "WebHandlerImpl.h"
bool RedirectWebHandler::canHandle(AsyncWebServerRequest *request)
AsyncStaticWebHandler::AsyncStaticWebHandler(FS& fs, const char* path, const char* uri, const char* cache_header)
: _fs(fs), _uri(uri), _path(path), _cache_header(cache_header)
{
// We can redirect when the request url match and ip doesn't match
if (request->url() == _url && _exclude_ip != request->client()->localIP()) {
DEBUGF("[RedirectWebHandler::canHandle] TRUE\n");
return true;
}
return false;
}
// Ensure leading '/'
if (_uri.length() == 0 || _uri[0] != '/') _uri = "/" + _uri;
if (_path.length() == 0 || _path[0] != '/') _path = "/" + _path;
void RedirectWebHandler::handleRequest(AsyncWebServerRequest *request)
{
AsyncWebServerResponse *response = request->beginResponse(302);
response->addHeader("Location", _location);
request->send(response);
}
// If uri or path ends with '/' we assume a hint that this is a directory to improve performance.
// However - if they both do not end '/' we, can't assume they are files, they can still be directory.
bool isUriDir = _uri[_uri.length()-1] == '/';
bool isPathDir = _path[_path.length()-1] == '/';
_isDir = isUriDir || isPathDir;
// If we serving directory - remove the trailing '/' so we can handle default file
// Notice that root will be "" not "/"
if (_isDir && isUriDir) _uri = _uri.substring(0, _uri.length()-1);
if (_isDir && isPathDir) _path = _path.substring(0, _path.length()-1);
// Reset stats
_gzipFirst = false;
_gzipStats = 0;
_fileStats = 0;
}
bool AsyncStaticWebHandler::canHandle(AsyncWebServerRequest *request)
{
if (request->method() != HTTP_GET) {
return false;
}
if ((_isFile && request->url() != _uri) ) {
return false;
}
// if the root of the request matches the _uri then it checks to see if there is a file it can handle.
if (request->url().startsWith(_uri)) {
String path = _getPath(request);
if (_fs.exists(path) || _fs.exists(path + ".gz")) {
if (_modified_header.length() != 0) {
request->addInterestingHeader("If-Modified-Since");
}
if (request->method() == HTTP_GET &&
request->url().startsWith(_uri) &&
_getFile(request)) {
DEBUGF("[AsyncStaticWebHandler::canHandle] TRUE\n");
return true;
}
}
return false;
}
String AsyncStaticWebHandler::_getPath(AsyncWebServerRequest *request)
bool AsyncStaticWebHandler::_getFile(AsyncWebServerRequest *request)
{
// Remove the found uri
String path = request->url().substring(_uri.length());
String path = request->url();
DEBUGF("[AsyncStaticWebHandler::_getPath]\n");
DEBUGF(" [stored] _uri = %s, _path = %s\n" , _uri.c_str(), _path.c_str() ) ;
DEBUGF(" [request] url = %s\n", request->url().c_str() );
// We can skip the file check if we serving a directory and (we have full match or we end with '/')
bool canSkipFileCheck = _isDir && (path.length() == 0 || path[path.length()-1] == '/');
if (!_isFile) {
DEBUGF(" _isFile = false\n");
String baserequestUrl = request->url().substring(_uri.length()); // this is the request - stored _uri... /espman/
DEBUGF(" baserequestUrl = %s\n", baserequestUrl.c_str());
path = _path + path;
if (!baserequestUrl.length()) {
baserequestUrl += "/";
// Do we have a file or .gz file
if (!canSkipFileCheck && _fileExists(request, path))
return true;
// Try to add default page, ensure there is a trailing '/' ot the path.
if (path.length() == 0 || path[path.length()-1] != '/')
path += "/";
path += "index.htm";
return _fileExists(request, path);
}
path = _path + baserequestUrl;
DEBUGF(" path = path + baserequestUrl, path = %s\n", path.c_str());
bool AsyncStaticWebHandler::_fileExists(AsyncWebServerRequest *request, const String path)
{
bool fileFound = false;
bool gzipFound = false;
if (path.endsWith("/")) {
DEBUGF(" 3 path ends with / : path = index.htm \n");
path += "index.htm";
String gzip = path + ".gz";
if (_gzipFirst) {
request->_tempFile = _fs.open(gzip, "r");
gzipFound = request->_tempFile == true;
if (!gzipFound){
request->_tempFile = _fs.open(path, "r");
fileFound = request->_tempFile == true;
}
} else {
path = _path;
request->_tempFile = _fs.open(path, "r");
fileFound = request->_tempFile == true;
if (!fileFound){
request->_tempFile = _fs.open(gzip, "r");
gzipFound = request->_tempFile == true;
}
}
DEBUGF(" final path = %s\n", path.c_str());
DEBUGF("[AsyncStaticWebHandler::_getPath] END\n\n");
bool found = fileFound || gzipFound;
return path;
if (found) {
size_t plen = path.length();
char * _tempPath = (char*)malloc(plen+1);
snprintf(_tempPath, plen+1, "%s", path.c_str());
request->_tempObject = (void*)_tempPath;
_gzipStats = (_gzipStats << 1) + gzipFound ? 1 : 0;
_fileStats = (_fileStats << 1) + fileFound ? 1 : 0;
_gzipFirst = _countBits(_gzipStats) > _countBits(_fileStats);
}
return found;
}
uint8_t AsyncStaticWebHandler::_countBits(const uint8_t value)
{
uint8_t w = value;
uint8_t n;
for (n=0; w!=0; n++) w&=w-1;
return n;
}
void AsyncStaticWebHandler::handleRequest(AsyncWebServerRequest *request)
{
String path = _getPath(request);
if (_fs.exists(path) || _fs.exists(path + ".gz")) {
if (_modified_header.length() != 0 && _modified_header == request->header("If-Modified-Since")) {
request->send(304); // Sed not modified
} else {
AsyncWebServerResponse * response = request->beginResponse(_fs, path);
if (_modified_header.length() !=0)
response->addHeader("Last-Modified", _modified_header);
if (request->_tempFile == true) {
AsyncWebServerResponse * response = new AsyncFileResponse(request->_tempFile, String((char*)request->_tempObject));
free(request->_tempObject);
request->_tempObject = NULL;
if (_cache_header.length() != 0)
response->addHeader("Cache-Control", _cache_header);
request->send(response);
}
} else {
request->send(404);
}
path = String();
}

View File

@@ -63,6 +63,7 @@ AsyncWebServerRequest::AsyncWebServerRequest(AsyncWebServer* s, AsyncClient* c)
, _itemBuffer(0)
, _itemBufferIndex(0)
, _itemIsFile(false)
, _tempObject(NULL)
, next(NULL)
{
c->onError([](void *r, AsyncClient* c, int8_t error){ AsyncWebServerRequest *req = (AsyncWebServerRequest*)r; req->_onError(error); }, this);
@@ -93,16 +94,30 @@ AsyncWebServerRequest::~AsyncWebServerRequest(){
delete _response;
}
if(_tempObject != NULL){
free(_tempObject);
}
}
void AsyncWebServerRequest::_onData(void *buf, size_t len){
if(_parseState < PARSE_REQ_BODY){
size_t i;
for(i=0; i<len; i++){
if(_parseState < PARSE_REQ_BODY)
_parseByte(((uint8_t*)buf)[i]);
else
return _onData((void *)((uint8_t*)buf+i), len-i);
// Find new line in buf
char *str = (char*)buf;
size_t i = 0;
for (; i < len; i++) if (str[i] == '\n') break;
if (i == len) { // No new line, just add the buffer in _temp
char ch = str[len-1];
str[len-1] = 0;
_temp.reserve(_temp.length()+len);
_temp.concat(str);
_temp.concat(ch);
} else { // Found new line - extract it and parse
str[i] = 0; // Terminate the string at the end of the line.
_temp.concat(str);
_temp.trim();
_parseLine();
if (++i < len) _onData(buf+i, len-i); // Still have more buffer to process
}
} else if(_parseState == PARSE_REQ_BODY){
if(_isMultipart){
@@ -139,7 +154,6 @@ void AsyncWebServerRequest::_onData(void *buf, size_t len){
_parseState = PARSE_REQ_END;
if(_handler) _handler->handleRequest(this);
else send(501);
return;
}
}
}
@@ -196,16 +210,23 @@ void AsyncWebServerRequest::_addGetParam(String param){
param = urlDecode(param);
String name = param;
String value = "";
if(param.indexOf('=') > 0){
name = param.substring(0, param.indexOf('='));
value = param.substring(param.indexOf('=') + 1);
int index = param.indexOf('=');
if(index > 0){
name = param.substring(0, index);
value = param.substring(index + 1);
}
_addParam(new AsyncWebParameter(name, value));
}
bool AsyncWebServerRequest::_parseReqHead(){
String m = _temp.substring(0, _temp.indexOf(' '));
// Split the head into method, url and version
int index = _temp.indexOf(' ');
String m = _temp.substring(0, index);
index = _temp.indexOf(' ', index+1);
String u = _temp.substring(m.length()+1, index);
_temp = _temp.substring(index+1);
if(m == "GET"){
_method = HTTP_GET;
} else if(m == "POST"){
@@ -222,22 +243,22 @@ bool AsyncWebServerRequest::_parseReqHead(){
_method = HTTP_OPTIONS;
}
_temp = _temp.substring(_temp.indexOf(' ')+1);
String u = _temp.substring(0, _temp.indexOf(' '));
u = urlDecode(u);
String g = String();
if(u.indexOf('?') > 0){
g = u.substring(u.indexOf('?') + 1);
u = u.substring(0, u.indexOf('?'));
index = u.indexOf('?');
if(index > 0){
g = u.substring(index+1);
u = u.substring(0, index);
}
_url = u;
if(g.length()){
while(true){
if(g.length() == 0)
break;
if(g.indexOf('&') > 0){
_addGetParam(g.substring(0, g.indexOf('&')));
g = g.substring(g.indexOf('&') + 1);
index = g.indexOf('&');
if(index > 0){
_addGetParam(g.substring(0, index));
g = g.substring(index+1);
} else {
_addGetParam(g);
break;
@@ -245,7 +266,6 @@ bool AsyncWebServerRequest::_parseReqHead(){
}
}
_temp = _temp.substring(_temp.indexOf(' ')+1);
if(_temp.startsWith("HTTP/1.1"))
_version = 1;
_temp = String();
@@ -253,36 +273,32 @@ bool AsyncWebServerRequest::_parseReqHead(){
}
bool AsyncWebServerRequest::_parseReqHeader(){
if(_temp.indexOf(':')){
AsyncWebHeader *h = new AsyncWebHeader(_temp);
if(h == NULL)
return false;
if(h->name() == "Host"){
_host = h->value();
delete h;
int index = _temp.indexOf(':');
if(index){
String name = _temp.substring(0, index);
String value = _temp.substring(index + 2);
if(name == "Host"){
_host = value;
_server->_handleRequest(this);
} else if(h->name() == "Content-Type"){
if (h->value().startsWith("multipart/")){
_boundary = h->value().substring(h->value().indexOf('=')+1);
_contentType = h->value().substring(0, h->value().indexOf(';'));
} else if(name == "Content-Type"){
if (value.startsWith("multipart/")){
_boundary = value.substring(value.indexOf('=')+1);
_contentType = value.substring(0, value.indexOf(';'));
_isMultipart = true;
} else {
_contentType = h->value();
_contentType = value;
}
delete h;
} else if(h->name() == "Content-Length"){
_contentLength = atoi(h->value().c_str());
delete h;
} else if(h->name() == "Expect" && h->value() == "100-continue"){
} else if(name == "Content-Length"){
_contentLength = atoi(value.c_str());
} else if(name == "Expect" && value == "100-continue"){
_expectingContinue = true;
delete h;
} else if(h->name() == "Authorization"){
if(h->value().startsWith("Basic")){
_authorization = h->value().substring(6);
} else if(name == "Authorization"){
if(value.startsWith("Basic")){
_authorization = value.substring(6);
}
delete h;
} else {
if(_interestingHeaders->contains(h->name()) || _interestingHeaders->contains("ANY")){
if(_interestingHeaders->contains(name) || _interestingHeaders->contains("ANY")){
AsyncWebHeader *h = new AsyncWebHeader(name, value);
if(_headers == NULL)
_headers = h;
else {
@@ -290,10 +306,8 @@ bool AsyncWebServerRequest::_parseReqHeader(){
while(hs->next != NULL) hs = hs->next;
hs->next = h;
}
} else
delete h;
}
}
}
_temp = String();
return true;
@@ -527,14 +541,6 @@ void AsyncWebServerRequest::_parseLine(){
}
}
void AsyncWebServerRequest::_parseByte(uint8_t data){
if((char)data != '\r' && (char)data != '\n')
_temp += (char)data;
if((char)data == '\n')
_parseLine();
}
int AsyncWebServerRequest::headers(){
int i = 0;
@@ -646,6 +652,12 @@ AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(FS &fs, String pat
return NULL;
}
AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(File content, String path, String contentType, bool download){
if(content == true)
return new AsyncFileResponse(content, path, contentType, download);
return NULL;
}
AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(Stream &stream, String contentType, size_t len){
return new AsyncStreamResponse(stream, contentType, len);
}
@@ -674,6 +686,12 @@ void AsyncWebServerRequest::send(FS &fs, String path, String contentType, bool d
} else send(404);
}
void AsyncWebServerRequest::send(File content, String path, String contentType, bool download){
if(content == true){
send(beginResponse(content, path, contentType, download));
} else send(404);
}
void AsyncWebServerRequest::send(Stream &stream, String contentType, size_t len){
send(beginResponse(stream, contentType, len));
}
@@ -776,26 +794,24 @@ bool AsyncWebServerRequest::hasHeader(const char* name){
String AsyncWebServerRequest::urlDecode(const String& text){
String decoded = "";
char temp[] = "0x00";
unsigned int len = text.length();
unsigned int i = 0;
String decoded = String();
decoded.reserve(len); // Allocate the string internal buffer - never longer from source text
while (i < len){
char decodedChar;
char encodedChar = text.charAt(i++);
if ((encodedChar == '%') && (i + 1 < len)){
temp[2] = text.charAt(i++);
temp[3] = text.charAt(i++);
decodedChar = strtol(temp, NULL, 16);
} else {
if (encodedChar == '+'){
} else if (encodedChar == '+') {
decodedChar = ' ';
} else {
decodedChar = encodedChar; // normal ascii char
}
}
decoded += decodedChar;
decoded.concat(decodedChar);
}
return decoded;
}

View File

@@ -48,6 +48,7 @@ class AsyncFileResponse: public AsyncAbstractResponse {
void _setContentType(String path);
public:
AsyncFileResponse(FS &fs, String path, String contentType=String(), bool download=false);
AsyncFileResponse(File content, String path, String contentType=String(), bool download=false);
~AsyncFileResponse();
bool _sourceValid(){ return !!(_content); }
size_t _fillBuffer(uint8_t *buf, size_t maxLen);

View File

@@ -119,21 +119,31 @@ String AsyncWebServerResponse::_assembleHead(uint8_t version){
if(_chunked)
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";
String out = String();
int bufSize = 300;
char buf[bufSize];
if(_contentType.length())
out += "Content-Type: " + _contentType + "\r\n";
snprintf(buf, bufSize, "HTTP/1.%d %d %s\r\n", version, _code, _responseCodeToString(_code));
out.concat(buf);
if(_sendContentLength) {
snprintf(buf, bufSize, "Content-Length: %d\r\n", _contentLength);
out.concat(buf);
}
if(_contentType.length()) {
snprintf(buf, bufSize, "Content-Type: %s\r\n", _contentType.c_str());
out.concat(buf);
}
AsyncWebHeader *h;
while(_headers != NULL){
h = _headers;
_headers = _headers->next;
out += h->toString();
snprintf(buf, bufSize, "%s: %s\r\n", h->name().c_str(), h->value().c_str());
out.concat(buf);
delete h;
}
out += "\r\n";
out.concat("\r\n");
_headLength = out.length();
return out;
}
@@ -158,7 +168,6 @@ AsyncBasicResponse::AsyncBasicResponse(int code, String contentType, String cont
_contentType = "text/plain";
}
addHeader("Connection","close");
addHeader("Access-Control-Allow-Origin","*");
}
void AsyncBasicResponse::_respond(AsyncWebServerRequest *request){
@@ -228,7 +237,6 @@ size_t AsyncBasicResponse::_ack(AsyncWebServerRequest *request, size_t len, uint
void AsyncAbstractResponse::_respond(AsyncWebServerRequest *request){
addHeader("Connection","close");
addHeader("Access-Control-Allow-Origin","*");
_head = _assembleHead(request->version());
_state = RESPONSE_HEADERS;
_ack(request, 0, 0);
@@ -333,7 +341,6 @@ void AsyncFileResponse::_setContentType(String path){
if (path.endsWith(".html")) _contentType = "text/html";
else if (path.endsWith(".htm")) _contentType = "text/html";
else if (path.endsWith(".css")) _contentType = "text/css";
else if (path.endsWith(".txt")) _contentType = "text/plain";
else if (path.endsWith(".js")) _contentType = "application/javascript";
else if (path.endsWith(".png")) _contentType = "image/png";
else if (path.endsWith(".gif")) _contentType = "image/gif";
@@ -344,23 +351,64 @@ void AsyncFileResponse::_setContentType(String path){
else if (path.endsWith(".pdf")) _contentType = "application/pdf";
else if (path.endsWith(".zip")) _contentType = "application/zip";
else if(path.endsWith(".gz")) _contentType = "application/x-gzip";
else _contentType = "application/octet-stream";
else _contentType = "text/plain";
}
AsyncFileResponse::AsyncFileResponse(FS &fs, String path, String contentType, bool download){
_code = 200;
_path = path;
_content = fs.open(_path, "r");
_contentLength = _content.size();
if(!download && !fs.exists(_path) && fs.exists(_path+".gz")){
_path = _path+".gz";
addHeader("Content-Encoding", "gzip");
}
if(download)
_contentType = "application/octet-stream";
else
if(contentType == "")
_setContentType(path);
_content = fs.open(_path, "r");
else
_contentType = contentType;
int filenameStart = path.lastIndexOf('/') + 1;
char buf[26+path.length()-filenameStart];
char* filename = (char*)path.c_str() + filenameStart;
if(download) {
// set filename and force download
snprintf(buf, sizeof (buf), "attachment; filename=\"%s\"", filename);
} else {
// set filename and force rendering
snprintf(buf, sizeof (buf), "inline; filename=\"%s\"", filename);
}
addHeader("Content-Disposition", buf);
}
AsyncFileResponse::AsyncFileResponse(File content, String path, String contentType, bool download){
_code = 200;
_path = path;
_content = content;
_contentLength = _content.size();
if(!download && String(_content.name()).endsWith(".gz"))
addHeader("Content-Encoding", "gzip");
if(contentType == "")
_setContentType(path);
else
_contentType = contentType;
int filenameStart = path.lastIndexOf('/') + 1;
char buf[26+path.length()-filenameStart];
char* filename = (char*)path.c_str() + filenameStart;
if(download) {
snprintf(buf, sizeof (buf), "attachment; filename=\"%s\"", filename);
} else {
snprintf(buf, sizeof (buf), "inline; filename=\"%s\"", filename);
}
addHeader("Content-Disposition", buf);
}
size_t AsyncFileResponse::_fillBuffer(uint8_t *data, size_t len){
@@ -458,4 +506,3 @@ size_t AsyncResponseStream::write(const uint8_t *data, size_t len){
size_t AsyncResponseStream::write(uint8_t data){
return write(&data, 1);
}