123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526 |
- /*
- WebServer.cpp - Dead simple web-server.
- Supports only one simultaneous client, knows how to handle GET and POST.
- Copyright (c) 2014 Ivan Grokhotkov. All rights reserved.
- 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
- Modified 8 May 2015 by Hristo Gochkov (proper post and file upload handling)
- */
- #include <Arduino.h>
- #include <libb64/cencode.h>
- #include "WiFiServer.h"
- #include "WiFiClient.h"
- #include "WebServer.h"
- #include "FS.h"
- #include "detail/RequestHandlersImpl.h"
- //#define DEBUG_ESP_HTTP_SERVER
- #ifdef DEBUG_ESP_PORT
- #define DEBUG_OUTPUT DEBUG_ESP_PORT
- #else
- #define DEBUG_OUTPUT Serial
- #endif
- const char * AUTHORIZATION_HEADER = "Authorization";
- WebServer::WebServer(IPAddress addr, int port)
- : _server(addr, port)
- , _currentMethod(HTTP_ANY)
- , _currentVersion(0)
- , _currentStatus(HC_NONE)
- , _statusChange(0)
- , _currentHandler(0)
- , _firstHandler(0)
- , _lastHandler(0)
- , _currentArgCount(0)
- , _currentArgs(0)
- , _headerKeysCount(0)
- , _currentHeaders(0)
- , _contentLength(0)
- , _chunked(false)
- {
- }
- WebServer::WebServer(int port)
- : _server(port)
- , _currentMethod(HTTP_ANY)
- , _currentVersion(0)
- , _currentStatus(HC_NONE)
- , _statusChange(0)
- , _currentHandler(0)
- , _firstHandler(0)
- , _lastHandler(0)
- , _currentArgCount(0)
- , _currentArgs(0)
- , _headerKeysCount(0)
- , _currentHeaders(0)
- , _contentLength(0)
- , _chunked(false)
- {
- }
- WebServer::~WebServer() {
- if (_currentHeaders)
- delete[]_currentHeaders;
- _headerKeysCount = 0;
- RequestHandler* handler = _firstHandler;
- while (handler) {
- RequestHandler* next = handler->next();
- delete handler;
- handler = next;
- }
- close();
- }
- void WebServer::begin() {
- _currentStatus = HC_NONE;
- _server.begin();
- if(!_headerKeysCount)
- collectHeaders(0, 0);
- }
- bool WebServer::authenticate(const char * username, const char * password){
- if(hasHeader(AUTHORIZATION_HEADER)){
- String authReq = header(AUTHORIZATION_HEADER);
- if(authReq.startsWith("Basic")){
- authReq = authReq.substring(6);
- authReq.trim();
- char toencodeLen = strlen(username)+strlen(password)+1;
- char *toencode = new char[toencodeLen + 1];
- if(toencode == NULL){
- authReq = String();
- return false;
- }
- char *encoded = new char[base64_encode_expected_len(toencodeLen)+1];
- if(encoded == NULL){
- authReq = String();
- delete[] toencode;
- return false;
- }
- sprintf(toencode, "%s:%s", username, password);
- if(base64_encode_chars(toencode, toencodeLen, encoded) > 0 && authReq.equals(encoded)){
- authReq = String();
- delete[] toencode;
- delete[] encoded;
- return true;
- }
- delete[] toencode;
- delete[] encoded;
- }
- authReq = String();
- }
- return false;
- }
- void WebServer::requestAuthentication(){
- sendHeader("WWW-Authenticate", "Basic realm=\"Login Required\"");
- send(401);
- }
- void WebServer::on(const String &uri, WebServer::THandlerFunction handler) {
- on(uri, HTTP_ANY, handler);
- }
- void WebServer::on(const String &uri, HTTPMethod method, WebServer::THandlerFunction fn) {
- on(uri, method, fn, _fileUploadHandler);
- }
- void WebServer::on(const String &uri, HTTPMethod method, WebServer::THandlerFunction fn, WebServer::THandlerFunction ufn) {
- _addRequestHandler(new FunctionRequestHandler(fn, ufn, uri, method));
- }
- void WebServer::addHandler(RequestHandler* handler) {
- _addRequestHandler(handler);
- }
- void WebServer::_addRequestHandler(RequestHandler* handler) {
- if (!_lastHandler) {
- _firstHandler = handler;
- _lastHandler = handler;
- }
- else {
- _lastHandler->next(handler);
- _lastHandler = handler;
- }
- }
- void WebServer::serveStatic(const char* uri, FS& fs, const char* path, const char* cache_header) {
- _addRequestHandler(new StaticRequestHandler(fs, path, uri, cache_header));
- }
- void WebServer::handleClient() {
- if (_currentStatus == HC_NONE) {
- WiFiClient client = _server.available();
- if (!client) {
- return;
- }
- #ifdef DEBUG_ESP_HTTP_SERVER
- DEBUG_OUTPUT.println("New client");
- #endif
- _currentClient = client;
- _currentStatus = HC_WAIT_READ;
- _statusChange = millis();
- }
- if (!_currentClient.connected()) {
- _currentClient = WiFiClient();
- _currentStatus = HC_NONE;
- return;
- }
- // Wait for data from client to become available
- if (_currentStatus == HC_WAIT_READ) {
- if (!_currentClient.available()) {
- if (millis() - _statusChange > HTTP_MAX_DATA_WAIT) {
- _currentClient = WiFiClient();
- _currentStatus = HC_NONE;
- }
- yield();
- return;
- }
- if (!_parseRequest(_currentClient)) {
- _currentClient = WiFiClient();
- _currentStatus = HC_NONE;
- return;
- }
- _currentClient.setTimeout(HTTP_MAX_SEND_WAIT);
- _contentLength = CONTENT_LENGTH_NOT_SET;
- _handleRequest();
- if (!_currentClient.connected()) {
- _currentClient = WiFiClient();
- _currentStatus = HC_NONE;
- return;
- } else {
- _currentStatus = HC_WAIT_CLOSE;
- _statusChange = millis();
- return;
- }
- }
- if (_currentStatus == HC_WAIT_CLOSE) {
- if (millis() - _statusChange > HTTP_MAX_CLOSE_WAIT) {
- _currentClient = WiFiClient();
- _currentStatus = HC_NONE;
- } else {
- yield();
- return;
- }
- }
- }
- void WebServer::close() {
- _server.end();
- }
- void WebServer::stop() {
- close();
- }
- void WebServer::sendHeader(const String& name, const String& value, bool first) {
- String headerLine = name;
- headerLine += ": ";
- headerLine += value;
- headerLine += "\r\n";
- if (first) {
- _responseHeaders = headerLine + _responseHeaders;
- }
- else {
- _responseHeaders += headerLine;
- }
- }
- void WebServer::setContentLength(size_t contentLength) {
- _contentLength = contentLength;
- }
- void WebServer::_prepareHeader(String& response, int code, const char* content_type, size_t contentLength) {
- response = "HTTP/1."+String(_currentVersion)+" ";
- response += String(code);
- response += " ";
- response += _responseCodeToString(code);
- response += "\r\n";
- if (!content_type)
- content_type = "text/html";
- sendHeader("Content-Type", content_type, true);
- if (_contentLength == CONTENT_LENGTH_NOT_SET) {
- sendHeader("Content-Length", String(contentLength));
- } else if (_contentLength != CONTENT_LENGTH_UNKNOWN) {
- sendHeader("Content-Length", String(_contentLength));
- } else if(_contentLength == CONTENT_LENGTH_UNKNOWN && _currentVersion){ //HTTP/1.1 or above client
- //let's do chunked
- _chunked = true;
- sendHeader("Accept-Ranges","none");
- sendHeader("Transfer-Encoding","chunked");
- }
- sendHeader("Connection", "close");
- response += _responseHeaders;
- response += "\r\n";
- _responseHeaders = String();
- }
- void WebServer::send(int code, const char* content_type, const String& content) {
- String header;
- // Can we asume the following?
- //if(code == 200 && content.length() == 0 && _contentLength == CONTENT_LENGTH_NOT_SET)
- // _contentLength = CONTENT_LENGTH_UNKNOWN;
- _prepareHeader(header, code, content_type, content.length());
- _currentClient.write(header.c_str(), header.length());
- if(content.length())
- sendContent(content);
- }
- void WebServer::send_P(int code, PGM_P content_type, PGM_P content) {
- size_t contentLength = 0;
- if (content != NULL) {
- contentLength = strlen_P(content);
- }
- String header;
- char type[64];
- memccpy_P((void*)type, (PGM_VOID_P)content_type, 0, sizeof(type));
- _prepareHeader(header, code, (const char* )type, contentLength);
- _currentClient.write(header.c_str(), header.length());
- sendContent_P(content);
- }
- void WebServer::send_P(int code, PGM_P content_type, PGM_P content, size_t contentLength) {
- String header;
- char type[64];
- memccpy_P((void*)type, (PGM_VOID_P)content_type, 0, sizeof(type));
- _prepareHeader(header, code, (const char* )type, contentLength);
- sendContent(header);
- sendContent_P(content, contentLength);
- }
- void WebServer::send(int code, char* content_type, const String& content) {
- send(code, (const char*)content_type, content);
- }
- void WebServer::send(int code, const String& content_type, const String& content) {
- send(code, (const char*)content_type.c_str(), content);
- }
- void WebServer::sendContent(const String& content) {
- const char * footer = "\r\n";
- size_t len = content.length();
- if(_chunked) {
- char * chunkSize = (char *)malloc(11);
- if(chunkSize){
- sprintf(chunkSize, "%x%s", len, footer);
- _currentClient.write(chunkSize, strlen(chunkSize));
- free(chunkSize);
- }
- }
- _currentClient.write(content.c_str(), len);
- if(_chunked){
- _currentClient.write(footer, 2);
- }
- }
- void WebServer::sendContent_P(PGM_P content) {
- sendContent_P(content, strlen_P(content));
- }
- void WebServer::sendContent_P(PGM_P content, size_t size) {
- const char * footer = "\r\n";
- if(_chunked) {
- char * chunkSize = (char *)malloc(11);
- if(chunkSize){
- sprintf(chunkSize, "%x%s", size, footer);
- _currentClient.write(chunkSize, strlen(chunkSize));
- free(chunkSize);
- }
- }
- _currentClient.write(content, size);
- if(_chunked){
- _currentClient.write(footer, 2);
- }
- }
- String WebServer::arg(String name) {
- for (int i = 0; i < _currentArgCount; ++i) {
- if ( _currentArgs[i].key == name )
- return _currentArgs[i].value;
- }
- return String();
- }
- String WebServer::arg(int i) {
- if (i < _currentArgCount)
- return _currentArgs[i].value;
- return String();
- }
- String WebServer::argName(int i) {
- if (i < _currentArgCount)
- return _currentArgs[i].key;
- return String();
- }
- int WebServer::args() {
- return _currentArgCount;
- }
- bool WebServer::hasArg(String name) {
- for (int i = 0; i < _currentArgCount; ++i) {
- if (_currentArgs[i].key == name)
- return true;
- }
- return false;
- }
- String WebServer::header(String name) {
- for (int i = 0; i < _headerKeysCount; ++i) {
- if (_currentHeaders[i].key.equalsIgnoreCase(name))
- return _currentHeaders[i].value;
- }
- return String();
- }
- void WebServer::collectHeaders(const char* headerKeys[], const size_t headerKeysCount) {
- _headerKeysCount = headerKeysCount + 1;
- if (_currentHeaders)
- delete[]_currentHeaders;
- _currentHeaders = new RequestArgument[_headerKeysCount];
- _currentHeaders[0].key = AUTHORIZATION_HEADER;
- for (int i = 1; i < _headerKeysCount; i++){
- _currentHeaders[i].key = headerKeys[i-1];
- }
- }
- String WebServer::header(int i) {
- if (i < _headerKeysCount)
- return _currentHeaders[i].value;
- return String();
- }
- String WebServer::headerName(int i) {
- if (i < _headerKeysCount)
- return _currentHeaders[i].key;
- return String();
- }
- int WebServer::headers() {
- return _headerKeysCount;
- }
- bool WebServer::hasHeader(String name) {
- for (int i = 0; i < _headerKeysCount; ++i) {
- if ((_currentHeaders[i].key.equalsIgnoreCase(name)) && (_currentHeaders[i].value.length() > 0))
- return true;
- }
- return false;
- }
- String WebServer::hostHeader() {
- return _hostHeader;
- }
- void WebServer::onFileUpload(THandlerFunction fn) {
- _fileUploadHandler = fn;
- }
- void WebServer::onNotFound(THandlerFunction fn) {
- _notFoundHandler = fn;
- }
- void WebServer::_handleRequest() {
- bool handled = false;
- if (!_currentHandler){
- #ifdef DEBUG_ESP_HTTP_SERVER
- DEBUG_OUTPUT.println("request handler not found");
- #endif
- }
- else {
- handled = _currentHandler->handle(*this, _currentMethod, _currentUri);
- #ifdef DEBUG_ESP_HTTP_SERVER
- if (!handled) {
- DEBUG_OUTPUT.println("request handler failed to handle request");
- }
- #endif
- }
- if (!handled) {
- if(_notFoundHandler) {
- _notFoundHandler();
- }
- else {
- send(404, "text/plain", String("Not found: ") + _currentUri);
- }
- }
- _currentUri = String();
- }
- String WebServer::_responseCodeToString(int code) {
- switch (code) {
- case 100: return F("Continue");
- case 101: return F("Switching Protocols");
- case 200: return F("OK");
- case 201: return F("Created");
- case 202: return F("Accepted");
- case 203: return F("Non-Authoritative Information");
- case 204: return F("No Content");
- case 205: return F("Reset Content");
- case 206: return F("Partial Content");
- case 300: return F("Multiple Choices");
- case 301: return F("Moved Permanently");
- case 302: return F("Found");
- case 303: return F("See Other");
- case 304: return F("Not Modified");
- case 305: return F("Use Proxy");
- case 307: return F("Temporary Redirect");
- case 400: return F("Bad Request");
- case 401: return F("Unauthorized");
- case 402: return F("Payment Required");
- case 403: return F("Forbidden");
- case 404: return F("Not Found");
- case 405: return F("Method Not Allowed");
- case 406: return F("Not Acceptable");
- case 407: return F("Proxy Authentication Required");
- case 408: return F("Request Time-out");
- case 409: return F("Conflict");
- case 410: return F("Gone");
- case 411: return F("Length Required");
- case 412: return F("Precondition Failed");
- case 413: return F("Request Entity Too Large");
- case 414: return F("Request-URI Too Large");
- case 415: return F("Unsupported Media Type");
- case 416: return F("Requested range not satisfiable");
- case 417: return F("Expectation Failed");
- case 500: return F("Internal Server Error");
- case 501: return F("Not Implemented");
- case 502: return F("Bad Gateway");
- case 503: return F("Service Unavailable");
- case 504: return F("Gateway Time-out");
- case 505: return F("HTTP Version not supported");
- default: return "";
- }
- }
|