From ba45a834e982fb05994a24872253acc9fff4fe8b Mon Sep 17 00:00:00 2001 From: sticilface Date: Fri, 10 Jun 2016 08:46:12 +0100 Subject: [PATCH] =?UTF-8?q?=20=20This=20handler=20will=20serve=20a=20302?= =?UTF-8?q?=20response=20to=20a=20client=20request=20for=20a=20SP=E2=80=A6?= =?UTF-8?q?=20(#36)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * This handler will serve a 302 response to a client request for a SPIFFS file if the request comes from the STA side of the ESP network. If the request comes from the AP side then it serves the file from SPIFFS. * corrections. plus readme * Update README.md extra space * move wifi to cpp --- README.md | 20 ++++++++ src/FileFallbackHandler.cpp | 97 +++++++++++++++++++++++++++++++++++++ src/FileFallbackHandler.h | 55 +++++++++++++++++++++ 3 files changed, 172 insertions(+) create mode 100644 src/FileFallbackHandler.cpp create mode 100644 src/FileFallbackHandler.h diff --git a/README.md b/README.md index 1a15829..364f475 100644 --- a/README.md +++ b/README.md @@ -397,6 +397,26 @@ response->setLength(); request->send(response); ``` +### FileFallBackHandler +Example provided by [@sticilface](https://github.com/sticilface) + +This handler is useful for serving content from a CDN when the ESP is connected to a wifi +network, but falling back to local copies of the file stored in SPIFFS when the ESP is in +AP mode and the client does not have internet access. It will work when both AP mode and +STA mode are active. It works by returning 302 HTTP code, with a Location header that +you specify. It is much quicker than requiring the ESP to handle all the files. +```cpp +#include "FileFallbackHandler.h" // include this in the sketch. + +server.addHandler( new FileFallbackHandler(SPIFFS, "/path_to_SPIFFS_file", "/uri", "url_to_forward", "optional_cache_control_header")); + +// These three lines will serve all the jquery requirements from SPIFFS (if they are there) in AP mode, but forward the URL to CDN if not. +server.addHandler( new FileFallbackHandler(_fs, "/jquery/jqm1.4.5.css", "/jquery/jqm1.4.5.css", "http://code.jquery.com/mobile/1.4.5/jquery.mobile-1.4.5.min.css", "max-age=86400")); +server.addHandler( new FileFallbackHandler(_fs, "/jquery/jq1.11.1.js" , "/jquery/jq1.11.1.js" , "http://code.jquery.com/jquery-1.11.1.min.js", "max-age=86400")); +server.addHandler( new FileFallbackHandler(_fs, "/jquery/jqm1.4.5.js" , "/jquery/jqm1.4.5.js" , "http://code.jquery.com/mobile/1.4.5/jquery.mobile-1.4.5.min.js", "max-age=86400")); +``` + + ## Bad Responses Some responses are implemented, but you should not use them, because they do not conform to HTTP. The following example will lead to unclean close of the connection and more time wasted diff --git a/src/FileFallbackHandler.cpp b/src/FileFallbackHandler.cpp new file mode 100644 index 0000000..7623740 --- /dev/null +++ b/src/FileFallbackHandler.cpp @@ -0,0 +1,97 @@ + + + +#include "FileFallbackHandler.h" +#include + +#if defined(ESP31B) +#include +#elif defined(ESP8266) +#include +#else +#error Platform not supported +#endif + +bool FileFallbackHandler::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")) { + DEBUGF("[FileFallbackHandler::canHandle] TRUE\n"); + return true; + } + } + + return false; +} + +String FileFallbackHandler::_getPath(AsyncWebServerRequest *request) +{ + + String path = request->url(); + DEBUGF("[FileFallbackHandler::_getPath]\n"); + DEBUGF(" [stored] _uri = %s, _path = %s\n" , _uri.c_str(), _path.c_str() ) ; + DEBUGF(" [request] url = %s\n", request->url().c_str() ); + + 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()); + + if (!baserequestUrl.length()) { + baserequestUrl += "/"; + } + + path = _path + baserequestUrl; + DEBUGF(" path = path + baserequestUrl, path = %s\n", path.c_str()); + + if (path.endsWith("/")) { + DEBUGF(" 3 path ends with / : path = index.htm \n"); + path += "index.htm"; + } + } else { + path = _path; + } + + DEBUGF(" final path = %s\n", path.c_str()); + DEBUGF("[FileFallbackHandler::_getPath] END\n\n"); + + return path; +} + + +void FileFallbackHandler::handleRequest(AsyncWebServerRequest *request) +{ + + String path = _getPath(request); + + + if ( request->client()->localIP() == WiFi.localIP() ) { + + AsyncWebServerResponse *response = request->beginResponse(302); //Sends 404 File Not Found + response->addHeader("Location", _forwardUri ); + if (_cache_header.length() != 0) { + response->addHeader("Cache-Control", _cache_header); + } + request->send(response); + + } else if (_fs.exists(path) || _fs.exists(path + ".gz")) { + AsyncWebServerResponse * response = request->beginResponse(_fs, path); + if (_cache_header.length() != 0) { + response->addHeader("Cache-Control", _cache_header); + } + request->send(response); + } else { + request->send(404); + } + + path = String(); + +} diff --git a/src/FileFallbackHandler.h b/src/FileFallbackHandler.h new file mode 100644 index 0000000..abb932c --- /dev/null +++ b/src/FileFallbackHandler.h @@ -0,0 +1,55 @@ +// FileFallbackHandler.h +/* + FileFallbackHandler Response to use with asyncwebserver + Written by Andrew Melvin (SticilFace), based on ServeStatic, with help from me-no-dev. + + This handler will serve a 302 response to a client request for a SPIFFS file if the request comes from the STA side of the ESP network. + If the request comes from the AP side then it serves the file from SPIFFS. + This is useful if you have content that is available from a CDN but you want it to work in AP mode. + This also speeds things up a lot as the ESP is not left serving files, and the client can cache them as well. + + + FileFallbackHandler(SPIFFS, File_location, Uri, fallback_uri, cache_control header (optional) ) + + Example of callback in use + + server.addHandler( new FileFallbackHandler(SPIFFS, "/jquery/jqm1.4.5.css", "/jquery/jqm1.4.5.css", "http://code.jquery.com/mobile/1.4.5/jquery.mobile-1.4.5.min.css", "max-age=86400")); + + +*/ +#ifndef ASYNC_FILEFALLBACK_H_ +#define ASYNC_FILEFALLBACK_H_ + +#include + + +class FileFallbackHandler: public AsyncWebHandler { + private: + String _getPath(AsyncWebServerRequest *request); + protected: + FS _fs; + String _uri; + String _path; + String _forwardUri; + String _cache_header; + bool _isFile; + public: + FileFallbackHandler(FS& fs, const char* path, const char* uri, const char* forwardUri ,const char* cache_header) + : _fs(fs), _uri(uri), _path(path), _forwardUri(forwardUri),_cache_header(cache_header){ + + _isFile = _fs.exists(path) || _fs.exists((String(path)+".gz").c_str()); + if (_uri != "/" && _uri.endsWith("/")) { + _uri = _uri.substring(0, _uri.length() - 1); + DEBUGF("[FileFallbackHandler] _uri / removed\n"); + } + if (_path != "/" && _path.endsWith("/")) { + _path = _path.substring(0, _path.length() - 1); + DEBUGF("[FileFallbackHandler] _path / removed\n"); + } + } + bool canHandle(AsyncWebServerRequest *request); + void handleRequest(AsyncWebServerRequest *request); + +}; + +#endif