You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
220 lines
7.5 KiB
220 lines
7.5 KiB
/* |
|
Asynchronous WebServer library for Espressif MCUs |
|
|
|
Copyright (c) 2016 Hristo Gochkov. All rights reserved. |
|
This file is part of the esp8266 core 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 |
|
*/ |
|
#include "ESPAsyncWebServer.h" |
|
#include "WebHandlerImpl.h" |
|
|
|
AsyncStaticWebHandler::AsyncStaticWebHandler(const char* uri, FS& fs, const char* path, const char* cache_control) |
|
: _fs(fs), _uri(uri), _path(path), _default_file("index.htm"), _cache_control(cache_control), _last_modified(""), _callback(nullptr) |
|
{ |
|
// Ensure leading '/' |
|
if (_uri.length() == 0 || _uri[0] != '/') _uri = "/" + _uri; |
|
if (_path.length() == 0 || _path[0] != '/') _path = "/" + _path; |
|
|
|
// If path ends with '/' we assume a hint that this is a directory to improve performance. |
|
// However - if it does not end with '/' we, can't assume a file, path can still be a directory. |
|
_isDir = _path[_path.length()-1] == '/'; |
|
|
|
// Remove the trailing '/' so we can handle default file |
|
// Notice that root will be "" not "/" |
|
if (_uri[_uri.length()-1] == '/') _uri = _uri.substring(0, _uri.length()-1); |
|
if (_path[_path.length()-1] == '/') _path = _path.substring(0, _path.length()-1); |
|
|
|
// Reset stats |
|
_gzipFirst = false; |
|
_gzipStats = 0xF8; |
|
} |
|
|
|
AsyncStaticWebHandler& AsyncStaticWebHandler::setIsDir(bool isDir){ |
|
_isDir = isDir; |
|
return *this; |
|
} |
|
|
|
AsyncStaticWebHandler& AsyncStaticWebHandler::setDefaultFile(const char* filename){ |
|
_default_file = String(filename); |
|
return *this; |
|
} |
|
|
|
AsyncStaticWebHandler& AsyncStaticWebHandler::setCacheControl(const char* cache_control){ |
|
_cache_control = String(cache_control); |
|
return *this; |
|
} |
|
|
|
AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(const char* last_modified){ |
|
_last_modified = String(last_modified); |
|
return *this; |
|
} |
|
|
|
AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(struct tm* last_modified){ |
|
char result[30]; |
|
strftime (result,30,"%a, %d %b %Y %H:%M:%S %Z", last_modified); |
|
return setLastModified((const char *)result); |
|
} |
|
|
|
#ifdef ESP8266 |
|
AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(time_t last_modified){ |
|
return setLastModified((struct tm *)gmtime(&last_modified)); |
|
} |
|
|
|
AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(){ |
|
time_t last_modified; |
|
if(time(&last_modified) == 0) //time is not yet set |
|
return *this; |
|
return setLastModified(last_modified); |
|
} |
|
#endif |
|
bool AsyncStaticWebHandler::canHandle(AsyncWebServerRequest *request){ |
|
if(request->method() != HTTP_GET |
|
|| !request->url().startsWith(_uri) |
|
|| !request->isExpectedRequestedConnType(RCT_DEFAULT, RCT_HTTP) |
|
){ |
|
return false; |
|
} |
|
if (_getFile(request)) { |
|
// We interested in "If-Modified-Since" header to check if file was modified |
|
if (_last_modified.length()) |
|
request->addInterestingHeader("If-Modified-Since"); |
|
|
|
if(_cache_control.length()) |
|
request->addInterestingHeader("If-None-Match"); |
|
|
|
DEBUGF("[AsyncStaticWebHandler::canHandle] TRUE\n"); |
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
bool AsyncStaticWebHandler::_getFile(AsyncWebServerRequest *request) |
|
{ |
|
// Remove the found uri |
|
String path = request->url().substring(_uri.length()); |
|
|
|
// We can skip the file check and look for default if request is to the root of a directory or that request path ends with '/' |
|
bool canSkipFileCheck = (_isDir && path.length() == 0) || (path.length() && path[path.length()-1] == '/'); |
|
|
|
path = _path + path; |
|
|
|
// Do we have a file or .gz file |
|
if (!canSkipFileCheck && _fileExists(request, path)) |
|
return true; |
|
|
|
// Can't handle if not default file |
|
if (_default_file.length() == 0) |
|
return false; |
|
|
|
// Try to add default file, ensure there is a trailing '/' ot the path. |
|
if (path.length() == 0 || path[path.length()-1] != '/') |
|
path += "/"; |
|
path += _default_file; |
|
|
|
return _fileExists(request, path); |
|
} |
|
|
|
#ifdef ESP32 |
|
#define FILE_IS_REAL(f) (f == true && !f.isDirectory()) |
|
#else |
|
#define FILE_IS_REAL(f) (f == true) |
|
#endif |
|
|
|
bool AsyncStaticWebHandler::_fileExists(AsyncWebServerRequest *request, const String& path) |
|
{ |
|
bool fileFound = false; |
|
bool gzipFound = false; |
|
|
|
String gzip = path + ".gz"; |
|
|
|
if (_gzipFirst) { |
|
request->_tempFile = _fs.open(gzip, "r"); |
|
gzipFound = FILE_IS_REAL(request->_tempFile); |
|
if (!gzipFound){ |
|
request->_tempFile = _fs.open(path, "r"); |
|
fileFound = FILE_IS_REAL(request->_tempFile); |
|
} |
|
} else { |
|
request->_tempFile = _fs.open(path, "r"); |
|
fileFound = FILE_IS_REAL(request->_tempFile); |
|
if (!fileFound){ |
|
request->_tempFile = _fs.open(gzip, "r"); |
|
gzipFound = FILE_IS_REAL(request->_tempFile); |
|
} |
|
} |
|
|
|
bool found = fileFound || gzipFound; |
|
|
|
if (found) { |
|
// Extract the file name from the path and keep it in _tempObject |
|
size_t pathLen = path.length(); |
|
char * _tempPath = (char*)malloc(pathLen+1); |
|
snprintf(_tempPath, pathLen+1, "%s", path.c_str()); |
|
request->_tempObject = (void*)_tempPath; |
|
|
|
// Calculate gzip statistic |
|
_gzipStats = (_gzipStats << 1) + (gzipFound ? 1 : 0); |
|
if (_gzipStats == 0x00) _gzipFirst = false; // All files are not gzip |
|
else if (_gzipStats == 0xFF) _gzipFirst = true; // All files are gzip |
|
else _gzipFirst = _countBits(_gzipStats) > 4; // IF we have more gzip files - try gzip first |
|
} |
|
|
|
return found; |
|
} |
|
|
|
uint8_t AsyncStaticWebHandler::_countBits(const uint8_t value) const |
|
{ |
|
uint8_t w = value; |
|
uint8_t n; |
|
for (n=0; w!=0; n++) w&=w-1; |
|
return n; |
|
} |
|
|
|
void AsyncStaticWebHandler::handleRequest(AsyncWebServerRequest *request) |
|
{ |
|
// Get the filename from request->_tempObject and free it |
|
String filename = String((char*)request->_tempObject); |
|
free(request->_tempObject); |
|
request->_tempObject = NULL; |
|
if((_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str())) |
|
return request->requestAuthentication(); |
|
|
|
if (request->_tempFile == true) { |
|
String etag = String(request->_tempFile.size()); |
|
if (_last_modified.length() && _last_modified == request->header("If-Modified-Since")) { |
|
request->_tempFile.close(); |
|
request->send(304); // Not modified |
|
} else if (_cache_control.length() && request->hasHeader("If-None-Match") && request->header("If-None-Match").equals(etag)) { |
|
request->_tempFile.close(); |
|
AsyncWebServerResponse * response = new AsyncBasicResponse(304); // Not modified |
|
response->addHeader("Cache-Control", _cache_control); |
|
response->addHeader("ETag", etag); |
|
request->send(response); |
|
} else { |
|
AsyncWebServerResponse * response = new AsyncFileResponse(request->_tempFile, filename, String(), false, _callback); |
|
if (_last_modified.length()) |
|
response->addHeader("Last-Modified", _last_modified); |
|
if (_cache_control.length()){ |
|
response->addHeader("Cache-Control", _cache_control); |
|
response->addHeader("ETag", etag); |
|
} |
|
request->send(response); |
|
} |
|
} else { |
|
request->send(404); |
|
} |
|
}
|
|
|