mirror of
https://github.com/esp8266/Arduino.git
synced 2025-04-19 23:22:16 +03:00
485 lines
14 KiB
C++
485 lines
14 KiB
C++
/*
|
|
ESP8266WebServer.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 "ESP8266WebServer.h"
|
|
#include "FS.h"
|
|
#include "detail/RequestHandlersImpl.h"
|
|
// #define DEBUG
|
|
#define DEBUG_OUTPUT Serial
|
|
|
|
const char * AUTHORIZATION_HEADER = "Authorization";
|
|
|
|
ESP8266WebServer::ESP8266WebServer(IPAddress addr, int port)
|
|
: _server(addr, port)
|
|
, _currentMethod(HTTP_ANY)
|
|
, _currentHandler(0)
|
|
, _firstHandler(0)
|
|
, _lastHandler(0)
|
|
, _currentArgCount(0)
|
|
, _currentArgs(0)
|
|
, _headerKeysCount(0)
|
|
, _currentHeaders(0)
|
|
, _contentLength(0)
|
|
{
|
|
}
|
|
|
|
ESP8266WebServer::ESP8266WebServer(int port)
|
|
: _server(port)
|
|
, _currentMethod(HTTP_ANY)
|
|
, _currentHandler(0)
|
|
, _firstHandler(0)
|
|
, _lastHandler(0)
|
|
, _currentArgCount(0)
|
|
, _currentArgs(0)
|
|
, _headerKeysCount(0)
|
|
, _currentHeaders(0)
|
|
, _contentLength(0)
|
|
{
|
|
}
|
|
|
|
ESP8266WebServer::~ESP8266WebServer() {
|
|
if (_currentHeaders)
|
|
delete[]_currentHeaders;
|
|
_headerKeysCount = 0;
|
|
RequestHandler* handler = _firstHandler;
|
|
while (handler) {
|
|
RequestHandler* next = handler->next();
|
|
delete handler;
|
|
handler = next;
|
|
}
|
|
}
|
|
|
|
void ESP8266WebServer::begin() {
|
|
_server.begin();
|
|
if(!_headerKeysCount)
|
|
collectHeaders(0, 0);
|
|
}
|
|
|
|
bool ESP8266WebServer::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];
|
|
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 ESP8266WebServer::requestAuthentication(){
|
|
sendHeader("WWW-Authenticate", "Basic realm=\"Login Required\"");
|
|
send(401);
|
|
}
|
|
|
|
void ESP8266WebServer::on(const char* uri, ESP8266WebServer::THandlerFunction handler) {
|
|
on(uri, HTTP_ANY, handler);
|
|
}
|
|
|
|
void ESP8266WebServer::on(const char* uri, HTTPMethod method, ESP8266WebServer::THandlerFunction fn) {
|
|
on(uri, method, fn, _fileUploadHandler);
|
|
}
|
|
|
|
void ESP8266WebServer::on(const char* uri, HTTPMethod method, ESP8266WebServer::THandlerFunction fn, ESP8266WebServer::THandlerFunction ufn) {
|
|
_addRequestHandler(new FunctionRequestHandler(fn, ufn, uri, method));
|
|
}
|
|
|
|
void ESP8266WebServer::addHandler(RequestHandler* handler) {
|
|
_addRequestHandler(handler);
|
|
}
|
|
|
|
void ESP8266WebServer::_addRequestHandler(RequestHandler* handler) {
|
|
if (!_lastHandler) {
|
|
_firstHandler = handler;
|
|
_lastHandler = handler;
|
|
}
|
|
else {
|
|
_lastHandler->next(handler);
|
|
_lastHandler = handler;
|
|
}
|
|
}
|
|
|
|
void ESP8266WebServer::serveStatic(const char* uri, FS& fs, const char* path, const char* cache_header) {
|
|
_addRequestHandler(new StaticRequestHandler(fs, path, uri, cache_header));
|
|
}
|
|
|
|
void ESP8266WebServer::handleClient() {
|
|
WiFiClient client = _server.available();
|
|
if (!client) {
|
|
return;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
DEBUG_OUTPUT.println("New client");
|
|
#endif
|
|
|
|
// Wait for data from client to become available
|
|
uint16_t maxWait = HTTP_MAX_DATA_WAIT;
|
|
while(client.connected() && !client.available() && maxWait--){
|
|
delay(1);
|
|
}
|
|
|
|
if (!_parseRequest(client)) {
|
|
return;
|
|
}
|
|
|
|
_currentClient = client;
|
|
_contentLength = CONTENT_LENGTH_NOT_SET;
|
|
_handleRequest();
|
|
}
|
|
|
|
void ESP8266WebServer::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 ESP8266WebServer::_prepareHeader(String& response, int code, const char* content_type, size_t contentLength) {
|
|
response = "HTTP/1.1 ";
|
|
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_UNKNOWN && _contentLength != CONTENT_LENGTH_NOT_SET) {
|
|
sendHeader("Content-Length", String(_contentLength));
|
|
}
|
|
else if (contentLength > 0){
|
|
sendHeader("Content-Length", String(contentLength));
|
|
}
|
|
sendHeader("Connection", "close");
|
|
sendHeader("Access-Control-Allow-Origin", "*");
|
|
|
|
response += _responseHeaders;
|
|
response += "\r\n";
|
|
_responseHeaders = String();
|
|
}
|
|
|
|
void ESP8266WebServer::send(int code, const char* content_type, const String& content) {
|
|
String header;
|
|
_prepareHeader(header, code, content_type, content.length());
|
|
sendContent(header);
|
|
|
|
sendContent(content);
|
|
}
|
|
|
|
void ESP8266WebServer::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);
|
|
sendContent(header);
|
|
sendContent_P(content);
|
|
}
|
|
|
|
void ESP8266WebServer::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 ESP8266WebServer::send(int code, char* content_type, const String& content) {
|
|
send(code, (const char*)content_type, content);
|
|
}
|
|
|
|
void ESP8266WebServer::send(int code, const String& content_type, const String& content) {
|
|
send(code, (const char*)content_type.c_str(), content);
|
|
}
|
|
|
|
void ESP8266WebServer::sendContent(const String& content) {
|
|
const size_t unit_size = HTTP_DOWNLOAD_UNIT_SIZE;
|
|
size_t size_to_send = content.length();
|
|
const char* send_start = content.c_str();
|
|
|
|
while (size_to_send) {
|
|
size_t will_send = (size_to_send < unit_size) ? size_to_send : unit_size;
|
|
size_t sent = _currentClient.write(send_start, will_send);
|
|
if (sent == 0) {
|
|
break;
|
|
}
|
|
size_to_send -= sent;
|
|
send_start += sent;
|
|
}
|
|
}
|
|
|
|
void ESP8266WebServer::sendContent_P(PGM_P content) {
|
|
char contentUnit[HTTP_DOWNLOAD_UNIT_SIZE + 1];
|
|
|
|
contentUnit[HTTP_DOWNLOAD_UNIT_SIZE] = '\0';
|
|
|
|
while (content != NULL) {
|
|
size_t contentUnitLen;
|
|
PGM_P contentNext;
|
|
|
|
// due to the memccpy signature, lots of casts are needed
|
|
contentNext = (PGM_P)memccpy_P((void*)contentUnit, (PGM_VOID_P)content, 0, HTTP_DOWNLOAD_UNIT_SIZE);
|
|
|
|
if (contentNext == NULL) {
|
|
// no terminator, more data available
|
|
content += HTTP_DOWNLOAD_UNIT_SIZE;
|
|
contentUnitLen = HTTP_DOWNLOAD_UNIT_SIZE;
|
|
}
|
|
else {
|
|
// reached terminator. Do not send the terminator
|
|
contentUnitLen = contentNext - contentUnit - 1;
|
|
content = NULL;
|
|
}
|
|
|
|
// write is so overloaded, had to use the cast to get it pick the right one
|
|
_currentClient.write((const char*)contentUnit, contentUnitLen);
|
|
}
|
|
}
|
|
|
|
void ESP8266WebServer::sendContent_P(PGM_P content, size_t size) {
|
|
char contentUnit[HTTP_DOWNLOAD_UNIT_SIZE + 1];
|
|
contentUnit[HTTP_DOWNLOAD_UNIT_SIZE] = '\0';
|
|
size_t remaining_size = size;
|
|
|
|
while (content != NULL && remaining_size > 0) {
|
|
size_t contentUnitLen = HTTP_DOWNLOAD_UNIT_SIZE;
|
|
|
|
if (remaining_size < HTTP_DOWNLOAD_UNIT_SIZE) contentUnitLen = remaining_size;
|
|
// due to the memcpy signature, lots of casts are needed
|
|
memcpy_P((void*)contentUnit, (PGM_VOID_P)content, contentUnitLen);
|
|
|
|
content += contentUnitLen;
|
|
remaining_size -= contentUnitLen;
|
|
|
|
// write is so overloaded, had to use the cast to get it pick the right one
|
|
_currentClient.write((const char*)contentUnit, contentUnitLen);
|
|
}
|
|
}
|
|
|
|
String ESP8266WebServer::arg(const char* name) {
|
|
for (int i = 0; i < _currentArgCount; ++i) {
|
|
if (_currentArgs[i].key == name)
|
|
return _currentArgs[i].value;
|
|
}
|
|
return String();
|
|
}
|
|
|
|
String ESP8266WebServer::arg(int i) {
|
|
if (i < _currentArgCount)
|
|
return _currentArgs[i].value;
|
|
return String();
|
|
}
|
|
|
|
String ESP8266WebServer::argName(int i) {
|
|
if (i < _currentArgCount)
|
|
return _currentArgs[i].key;
|
|
return String();
|
|
}
|
|
|
|
int ESP8266WebServer::args() {
|
|
return _currentArgCount;
|
|
}
|
|
|
|
bool ESP8266WebServer::hasArg(const char* name) {
|
|
for (int i = 0; i < _currentArgCount; ++i) {
|
|
if (_currentArgs[i].key == name)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
String ESP8266WebServer::header(const char* name) {
|
|
for (int i = 0; i < _headerKeysCount; ++i) {
|
|
if (_currentHeaders[i].key == name)
|
|
return _currentHeaders[i].value;
|
|
}
|
|
return String();
|
|
}
|
|
|
|
void ESP8266WebServer::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 ESP8266WebServer::header(int i) {
|
|
if (i < _headerKeysCount)
|
|
return _currentHeaders[i].value;
|
|
return String();
|
|
}
|
|
|
|
String ESP8266WebServer::headerName(int i) {
|
|
if (i < _headerKeysCount)
|
|
return _currentHeaders[i].key;
|
|
return String();
|
|
}
|
|
|
|
int ESP8266WebServer::headers() {
|
|
return _headerKeysCount;
|
|
}
|
|
|
|
bool ESP8266WebServer::hasHeader(const char* name) {
|
|
for (int i = 0; i < _headerKeysCount; ++i) {
|
|
if ((_currentHeaders[i].key == name) && (_currentHeaders[i].value.length() > 0))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
String ESP8266WebServer::hostHeader() {
|
|
return _hostHeader;
|
|
}
|
|
|
|
void ESP8266WebServer::onFileUpload(THandlerFunction fn) {
|
|
_fileUploadHandler = fn;
|
|
}
|
|
|
|
void ESP8266WebServer::onNotFound(THandlerFunction fn) {
|
|
_notFoundHandler = fn;
|
|
}
|
|
|
|
void ESP8266WebServer::_handleRequest() {
|
|
bool handled = false;
|
|
if (!_currentHandler){
|
|
#ifdef DEBUG
|
|
DEBUG_OUTPUT.println("request handler not found");
|
|
#endif
|
|
}
|
|
else {
|
|
handled = _currentHandler->handle(*this, _currentMethod, _currentUri);
|
|
#ifdef DEBUG
|
|
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);
|
|
}
|
|
}
|
|
|
|
uint16_t maxWait = HTTP_MAX_CLOSE_WAIT;
|
|
while(_currentClient.connected() && maxWait--) {
|
|
delay(1);
|
|
}
|
|
_currentClient = WiFiClient();
|
|
_currentUri = String();
|
|
}
|
|
|
|
const char* ESP8266WebServer::_responseCodeToString(int code) {
|
|
switch (code) {
|
|
case 100: return "Continue";
|
|
case 101: return "Switching Protocols";
|
|
case 200: return "OK";
|
|
case 201: return "Created";
|
|
case 202: return "Accepted";
|
|
case 203: return "Non-Authoritative Information";
|
|
case 204: return "No Content";
|
|
case 205: return "Reset Content";
|
|
case 206: return "Partial Content";
|
|
case 300: return "Multiple Choices";
|
|
case 301: return "Moved Permanently";
|
|
case 302: return "Found";
|
|
case 303: return "See Other";
|
|
case 304: return "Not Modified";
|
|
case 305: return "Use Proxy";
|
|
case 307: return "Temporary Redirect";
|
|
case 400: return "Bad Request";
|
|
case 401: return "Unauthorized";
|
|
case 402: return "Payment Required";
|
|
case 403: return "Forbidden";
|
|
case 404: return "Not Found";
|
|
case 405: return "Method Not Allowed";
|
|
case 406: return "Not Acceptable";
|
|
case 407: return "Proxy Authentication Required";
|
|
case 408: return "Request Time-out";
|
|
case 409: return "Conflict";
|
|
case 410: return "Gone";
|
|
case 411: return "Length Required";
|
|
case 412: return "Precondition Failed";
|
|
case 413: return "Request Entity Too Large";
|
|
case 414: return "Request-URI Too Large";
|
|
case 415: return "Unsupported Media Type";
|
|
case 416: return "Requested range not satisfiable";
|
|
case 417: return "Expectation Failed";
|
|
case 500: return "Internal Server Error";
|
|
case 501: return "Not Implemented";
|
|
case 502: return "Bad Gateway";
|
|
case 503: return "Service Unavailable";
|
|
case 504: return "Gateway Time-out";
|
|
case 505: return "HTTP Version not supported";
|
|
default: return "";
|
|
}
|
|
}
|