@ -12,8 +12,6 @@ Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
# if API_SUPPORT
# if API_SUPPORT
# include <vector>
# include "system.h"
# include "system.h"
# include "web.h"
# include "web.h"
# include "rpc.h"
# include "rpc.h"
@ -21,252 +19,725 @@ Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
# include <ESPAsyncTCP.h>
# include <ESPAsyncTCP.h>
# include <ArduinoJson.h>
# include <ArduinoJson.h>
constexpr size_t ApiPathSizeMax { 64ul } ;
std : : vector < Api > _apis ;
# include <algorithm>
# include <cstring>
# include <forward_list>
# include <vector>
// -----------------------------------------------------------------------------
// API
// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------
bool _asJson ( AsyncWebServerRequest * request ) {
bool asJson = false ;
if ( request - > hasHeader ( " Accept " ) ) {
AsyncWebHeader * h = request - > getHeader ( " Accept " ) ;
asJson = h - > value ( ) . equals ( " application/json " ) ;
PathParts : : PathParts ( const String & path ) :
_path ( path )
{
if ( ! _path . length ( ) ) {
_ok = false ;
return ;
}
PathPart : : Type type { PathPart : : Type : : Unknown } ;
size_t length { 0ul } ;
size_t offset { 0ul } ;
const char * p { _path . c_str ( ) } ;
if ( * p = = ' \0 ' ) {
goto error ;
}
_parts . reserve ( std : : count ( _path . begin ( ) , _path . end ( ) , ' / ' ) + 1 ) ;
start :
type = PathPart : : Type : : Unknown ;
length = 0 ;
offset = p - _path . c_str ( ) ;
switch ( * p ) {
case ' + ' :
goto parse_single_wildcard ;
case ' # ' :
goto parse_multi_wildcard ;
case ' / ' :
default :
goto parse_value ;
}
}
return asJson ;
}
void _onAPIsText ( AsyncWebServerRequest * request ) {
AsyncResponseStream * response = request - > beginResponseStream ( " text/plain " ) ;
char buffer [ ApiPathSizeMax ] = { 0 } ;
for ( auto & api : _apis ) {
sprintf_P ( buffer , PSTR ( " /api/%s \n " ) , api . path . c_str ( ) ) ;
response - > write ( buffer ) ;
parse_value :
type = PathPart : : Type : : Value ;
switch ( * p ) {
case ' + ' :
case ' # ' :
goto error ;
case ' / ' :
case ' \0 ' :
goto push_result ;
}
+ + p ;
+ + length ;
goto parse_value ;
parse_single_wildcard :
type = PathPart : : Type : : SingleWildcard ;
+ + p ;
switch ( * p ) {
case ' / ' :
+ + p ;
case ' \0 ' :
goto push_result ;
}
goto error ;
parse_multi_wildcard :
type = PathPart : : Type : : MultiWildcard ;
+ + p ;
if ( * p = = ' \0 ' ) {
goto push_result ;
}
goto error ;
push_result :
emplace_back ( type , offset , length ) ;
if ( * p = = ' / ' ) {
+ + p ;
goto start ;
} else if ( * p ! = ' \0 ' ) {
goto start ;
}
}
request - > send ( response ) ;
goto success ;
error :
_ok = false ;
_parts . clear ( ) ;
return ;
success :
_ok = true ;
}
}
constexpr size_t ApiJsonBufferSize = 1024 ;
// match when, for example, given the path 'topic/one/two/three' and pattern 'topic/+/two/+'
bool PathParts : : match ( const PathParts & path ) const {
if ( ! _ok | | ! path ) {
return false ;
}
void _onAPIsJson ( AsyncWebServerRequest * request ) {
auto lhs = begin ( ) ;
auto rhs = path . begin ( ) ;
DynamicJsonBuffer jsonBuffer ( ApiJsonBufferSize ) ;
JsonArray & root = jsonBuffer . createArray ( ) ;
auto lhs_end = end ( ) ;
auto rhs_end = path . end ( ) ;
char buffer [ ApiPathSizeMax ] = { 0 } ;
for ( auto & api : _apis ) {
sprintf ( buffer , " /api/%s " , api . path . c_str ( ) ) ;
root . add ( buffer ) ;
loop :
if ( lhs = = lhs_end ) {
goto check_end ;
}
}
AsyncResponseStream * response = request - > beginResponseStream ( " application/json " ) ;
root . printTo ( * response ) ;
request - > send ( response ) ;
switch ( ( * lhs ) . type ) {
case PathPart : : Type : : Value :
if (
( rhs ! = rhs_end )
& & ( ( * rhs ) . type = = PathPart : : Type : : Value )
& & ( ( * rhs ) . offset = = ( * lhs ) . offset )
& & ( ( * rhs ) . length = = ( * lhs ) . length )
) {
if ( 0 = = std : : memcmp (
_path . c_str ( ) + ( * lhs ) . offset ,
path . path ( ) . c_str ( ) + ( * rhs ) . offset ,
( * rhs ) . length ) )
{
std : : advance ( lhs , 1 ) ;
std : : advance ( rhs , 1 ) ;
goto loop ;
}
}
goto error ;
case PathPart : : Type : : SingleWildcard :
if (
( rhs ! = rhs_end )
& & ( ( * rhs ) . type = = PathPart : : Type : : Value )
) {
std : : advance ( lhs , 1 ) ;
std : : advance ( rhs , 1 ) ;
goto loop ;
}
goto error ;
case PathPart : : Type : : MultiWildcard :
if ( std : : next ( lhs ) = = lhs_end ) {
while ( rhs ! = rhs_end ) {
if ( ( * rhs ) . type ! = PathPart : : Type : : Value ) {
goto error ;
}
std : : advance ( rhs , 1 ) ;
}
lhs = lhs_end ;
break ;
}
goto error ;
case PathPart : : Type : : Unknown :
goto error ;
} ;
check_end :
if ( ( lhs = = lhs_end ) & & ( rhs = = rhs_end ) ) {
return true ;
}
error :
return false ;
}
}
void _onAPIs ( AsyncWebServerRequest * request ) {
String ApiRequest : : wildcard ( int index ) const {
if ( index < 0 ) {
index = std : : abs ( index + 1 ) ;
}
webLog ( request ) ;
if ( ! apiAuthenticate ( request ) ) return ;
if ( std : : abs ( index ) > = _pattern . parts ( ) . size ( ) ) {
return _empty_string ( ) ;
}
bool asJson = _asJson ( request ) ;
int counter { 0 } ;
auto & pattern = _pattern . parts ( ) ;
String output ;
if ( asJson ) {
_onAPIsJson ( request ) ;
} else {
_onAPIsText ( request ) ;
for ( unsigned int part = 0 ; part < pattern . size ( ) ; + + part ) {
auto & lhs = pattern [ part ] ;
if ( PathPart : : Type : : SingleWildcard = = lhs . type ) {
if ( counter = = index ) {
auto & rhs = _parts . parts ( ) [ part ] ;
return _parts . path ( ) . substring ( rhs . offset , rhs . offset + rhs . length ) ;
}
+ + counter ;
}
}
}
return _empty_string ( ) ;
}
}
void _onRPC ( AsyncWebServerRequest * request ) {
size_t ApiRequest : : wildcards ( ) const {
size_t result { 0ul } ;
for ( auto & part : _pattern ) {
if ( PathPart : : Type : : SingleWildcard = = part . type ) {
+ + result ;
}
}
webLog ( request ) ;
if ( ! apiAuthenticate ( request ) ) return ;
return result ;
}
//bool asJson = _asJson(request);
int response = 404 ;
// -----------------------------------------------------------------------------
if ( request - > hasParam ( " action " ) ) {
bool _apiAccepts ( AsyncWebServerRequest * request , const __FlashStringHelper * str ) {
auto * header = request - > getHeader ( F ( " Accept " ) ) ;
if ( header ) {
return
( header - > value ( ) . indexOf ( F ( " */* " ) ) > = 0 )
| | ( header - > value ( ) . indexOf ( str ) > = 0 ) ;
}
AsyncWebParameter * p = request - > getParam ( " action " ) ;
return false ;
}
const auto action = p - > value ( ) ;
DEBUG_MSG_P ( PSTR ( " [RPC] Action: %s \n " ) , action . c_str ( ) ) ;
bool _apiAcceptsText ( AsyncWebServerRequest * request ) {
return _apiAccepts ( request , F ( " text/plain " ) ) ;
}
if ( rpcHandleAction ( action ) ) {
response = 204 ;
}
bool _apiAcceptsJson ( AsyncWebServerRequest * request ) {
return _apiAccepts ( request , F ( " application/json " ) ) ;
}
bool _apiMatchHeader ( AsyncWebServerRequest * request , const __FlashStringHelper * key , const __FlashStringHelper * value ) {
auto * header = request - > getHeader ( key ) ;
if ( header ) {
return header - > value ( ) . equals ( value ) ;
}
}
request - > send ( response ) ;
return false ;
}
bool _apiIsJsonContent ( AsyncWebServerRequest * request ) {
return _apiMatchHeader ( request , F ( " Content-Type " ) , F ( " application/json " ) ) ;
}
}
struct ApiMatch {
Api * api { nullptr } ;
Api : : Type type { Api : : Type : : Basic } ;
} ;
bool _apiIsFormDataContent ( AsyncWebServerRequest * request ) {
return _apiMatchHeader ( request , F ( " Content-Type " ) , F ( " application/x-www-form-urlencoded " ) ) ;
}
ApiMatch _apiMatch ( const String & url , AsyncWebServerRequest * request ) {
struct ApiRequestHelper {
ApiRequestHelper ( const ApiRequestHelper & ) = delete ;
ApiRequestHelper ( ApiRequestHelper & & ) noexcept = default ;
ApiMatch result ;
char buffer [ ApiPathSizeMax ] = { 0 } ;
// &path is expected to be request->url(), which is valid throughout the request's lifetime
explicit ApiRequestHelper ( AsyncWebServerRequest & request , const PathParts & pattern ) :
_request ( request ) ,
_pattern ( pattern ) ,
_path ( request . url ( ) ) ,
_match ( _pattern . match ( _path ) )
{ }
for ( auto & api : _apis ) {
sprintf_P ( buffer , PSTR ( " /api/%s " ) , api . path . c_str ( ) ) ;
if ( url ! = buffer ) {
continue ;
}
ApiRequest request ( ) const {
return ApiRequest ( _request , _pattern , _path ) ;
}
auto type = _asJson ( request )
? Api : : Type : : Json
: Api : : Type : : Basic ;
const PathParts & parts ( ) const {
return _path ;
}
result . api = & api ;
result . type = type ;
break ;
bool match ( ) const {
return _match ;
}
}
return result ;
private :
AsyncWebServerRequest & _request ;
const PathParts & _pattern ;
PathParts _path ;
bool _match ;
} ;
// Because the webserver request is split between multiple separate function invocations, we need to preserve some state.
// TODO: in case we are dealing with multicore, perhaps enforcing static-size data structs instead of the vector would we better,
// to avoid calling generic malloc when paths are parsed?
//
// Some quirks to deal with:
// - handleBody is called before handleRequest, and there's no way to signal completion / success of both callbacks to the server
// - Server never checks for request closing in filter or canHandle, so if we don't want to handle large content-length, it
// will still flow through the lwip backend.
// - `request->_tempObject` is used to keep API request state, but it's just a plain void pointer
// - espasyncwebserver will `free(_tempObject)` when request is disconnected, but only after this callbackhandler is done.
// make sure it's set to nullptr via `AsyncWebServerRequest::onDisconnect`
// - ALL headers are parsed (and we could access those during filter and canHandle callbacks), but we need to explicitly
// request them to stay in memory so that the actual handler can work with them
void _apiAttachHelper ( AsyncWebServerRequest & request , ApiRequestHelper & & helper ) {
request . _tempObject = new ApiRequestHelper ( std : : move ( helper ) ) ;
request . onDisconnect ( [ & ] ( ) {
auto * ptr = reinterpret_cast < ApiRequestHelper * > ( request . _tempObject ) ;
delete ptr ;
request . _tempObject = nullptr ;
} ) ;
request . addInterestingHeader ( F ( " Api-Key " ) ) ;
}
}
bool _apiDispatchRequest ( const String & url , AsyncWebServerRequest * request ) {
class ApiBaseWebHandler : public AsyncWebHandler {
public :
ApiBaseWebHandler ( ) = delete ;
ApiBaseWebHandler ( const ApiBaseWebHandler & ) = delete ;
ApiBaseWebHandler ( ApiBaseWebHandler & & ) = delete ;
auto match = _apiMatch ( url , request ) ;
if ( ! match . api ) {
return false ;
// In case this needs to be copied or moved, ensure PathParts copy references the new object's string
template < typename Pattern >
explicit ApiBaseWebHandler ( Pattern & & pattern ) :
_pattern ( std : : forward < Pattern > ( pattern ) ) ,
_parts ( _pattern )
{ }
const String & pattern ( ) const {
return _pattern ;
}
}
if ( match . type ! = match . api - > type ) {
DEBUG_MSG_P ( PSTR ( " [API] Cannot handle the request type \n " ) ) ;
request - > send ( 404 ) ;
return true ;
const PathParts & parts ( ) const {
return _parts ;
}
}
const bool is_put = (
( ! apiRestFul ( ) | | ( request - > method ( ) = = HTTP_PUT ) )
& & request - > hasParam ( " value " , request - > method ( ) = = HTTP_PUT )
) ;
private :
String _pattern ;
PathParts _parts ;
} ;
ApiBuffer buffer ;
// 'Modernized' API configuration:
// - `Api-Key` header for both GET and PUT
// - Parse request body as JSON object. Limited to LWIP internal buffer size, and will also break when client
// does weird stuff and PUTs data in multiple packets b/c only the initial packet is parsed.
// - Same as the text/plain, when ApiRequest::handle was not called it will then call GET
//
// TODO: bump to arduinojson v6 to handle partial / broken data payloads
// TODO: somehow detect partial data and buffer (optionally)
// TODO: POST instead of PUT?
class ApiJsonWebHandler final : public ApiBaseWebHandler {
public :
static constexpr size_t BufferSize { API_JSON_BUFFER_SIZE } ;
struct ReadOnlyStream : public Stream {
ReadOnlyStream ( ) = delete ;
explicit ReadOnlyStream ( const uint8_t * buffer , size_t size ) :
_buffer ( buffer ) ,
_size ( size )
{ }
int available ( ) override {
return _size - _index ;
}
switch ( match . api - > type ) {
int peek ( ) override {
if ( _index < _size ) {
return static_cast < int > ( _buffer [ _index ] ) ;
}
case Api : : Type : : Basic : {
if ( ! match . api - > get . basic ) {
break ;
return - 1 ;
}
}
if ( is_put ) {
if ( ! match . api - > put . basic ) {
break ;
int read ( ) override {
auto peeked = peek ( ) ;
if ( peeked > = 0 ) {
+ + _index ;
}
}
auto value = request - > getParam ( " value " , request - > method ( ) = = HTTP_PUT ) - > value ( ) ;
if ( buffer . size < ( value . length ( ) + 1ul ) ) {
break ;
return peeked ;
}
// since we are fixed in size, no need for any timeouts and the only available option is to return full chunk of data
size_t readBytes ( uint8_t * ptr , size_t size ) override {
if ( ( _index < _size ) & & ( ( _size - _index ) > = size ) ) {
std : : copy ( _buffer + _index , _buffer + _index + size , ptr ) ;
_index + = size ;
return size ;
}
}
std : : copy ( value . c_str ( ) , value . c_str ( ) + value . length ( ) + 1 , buffer . data ) ;
match . api - > put . basic ( * match . api , buffer ) ;
buffer . erase ( ) ;
return 0 ;
}
}
match . api - > get . basic ( * match . api , buffer ) ;
request - > send ( 200 , " text/plain " , buffer . data ) ;
size_t readBytes ( char * ptr , size_t size ) override {
return readBytes ( reinterpret_cast < uint8_t * > ( ptr ) , size ) ;
}
void flush ( ) override {
}
size_t write ( const uint8_t * , size_t ) override {
return 0 ;
}
size_t write ( uint8_t ) override {
return 0 ;
}
const uint8_t * _buffer ;
const size_t _size ;
size_t _index { 0 } ;
} ;
ApiJsonWebHandler ( ) = delete ;
ApiJsonWebHandler ( const ApiJsonWebHandler & ) = delete ;
ApiJsonWebHandler ( ApiJsonWebHandler & & ) = delete ;
template < typename Path , typename Callback >
ApiJsonWebHandler ( Path & & path , Callback & & get , Callback & & put ) :
ApiBaseWebHandler ( std : : forward < Path > ( path ) ) ,
_get ( std : : forward < Callback > ( get ) ) ,
_put ( std : : forward < Callback > ( put ) )
{ }
bool isRequestHandlerTrivial ( ) override {
return true ;
return true ;
}
}
// TODO: pass the body instead of `value` param
// TODO: handle HTTP_PUT
case Api : : Type : : Json : {
if ( ! match . api - > get . json | | is_put ) {
break ;
bool canHandle ( AsyncWebServerRequest * request ) override {
if ( ! apiEnabled ( ) ) {
return false ;
}
}
DynamicJsonBuffer jsonBuffer ( API_BUFFER_SIZE ) ;
if ( ! _apiAcceptsJson ( request ) ) {
return false ;
}
auto helper = ApiRequestHelper ( * request , parts ( ) ) ;
if ( helper . match ( ) & & apiAuthenticate ( request ) ) {
switch ( request - > method ( ) ) {
case HTTP_HEAD :
return true ;
case HTTP_PUT :
if ( ! _apiIsJsonContent ( request ) ) {
return false ;
}
if ( ! _put ) {
return false ;
}
case HTTP_GET :
if ( ! _get ) {
return false ;
}
break ;
default :
return false ;
}
_apiAttachHelper ( * request , std : : move ( helper ) ) ;
return true ;
}
return false ;
}
void _handleGet ( AsyncWebServerRequest * request , ApiRequest & apireq ) {
DynamicJsonBuffer jsonBuffer ( API_JSON_BUFFER_SIZE ) ;
JsonObject & root = jsonBuffer . createObject ( ) ;
JsonObject & root = jsonBuffer . createObject ( ) ;
if ( ! _get ( apireq , root ) ) {
request - > send ( 500 ) ;
return ;
}
match . api - > get . json ( * match . api , root ) ;
if ( ! apireq . done ( ) ) {
AsyncResponseStream * response = request - > beginResponseStream ( " application/json " , root . measureLength ( ) + 1 ) ;
root . printTo ( * response ) ;
request - > send ( response ) ;
return ;
}
AsyncResponseStream * response = request - > beginResponseStream ( " application/json " , root . measureLength ( ) + 1 ) ;
root . printTo ( * response ) ;
request - > send ( response ) ;
request - > send ( 500 ) ;
}
return true ;
void _handlePut ( AsyncWebServerRequest * request , uint8_t * data , size_t size ) {
// XXX: arduinojson v5 de-serializer will happily read garbage from raw ptr, since there's no length limit
// this is fixed in v6 though. for now, use a wrapper, but be aware that this actually uses more mem for the jsonbuffer
DynamicJsonBuffer jsonBuffer ( API_JSON_BUFFER_SIZE ) ;
ReadOnlyStream stream ( data , size ) ;
JsonObject & root = jsonBuffer . parseObject ( stream ) ;
if ( ! root . success ( ) ) {
request - > send ( 500 ) ;
return ;
}
auto & helper = * reinterpret_cast < ApiRequestHelper * > ( request - > _tempObject ) ;
auto apireq = helper . request ( ) ;
if ( ! _put ( apireq , root ) ) {
request - > send ( 500 ) ;
return ;
}
if ( ! apireq . done ( ) ) {
_handleGet ( request , apireq ) ;
}
return ;
}
}
void handleBody ( AsyncWebServerRequest * request , uint8_t * data , size_t len , size_t , size_t total ) override {
if ( total & & ( len = = total ) ) {
_handlePut ( request , data , total ) ;
}
}
}
DEBUG_MSG_P ( PSTR ( " [API] Method not supported \n " ) ) ;
request - > send ( 405 ) ;
void handleRequest ( AsyncWebServerRequest * request ) override {
auto & helper = * reinterpret_cast < ApiRequestHelper * > ( request - > _tempObject ) ;
return true ;
switch ( request - > method ( ) ) {
case HTTP_HEAD :
request - > send ( 204 ) ;
return ;
}
case HTTP_GET : {
auto apireq = helper . request ( ) ;
_handleGet ( request , apireq ) ;
return ;
}
bool _apiRequestCallback ( AsyncWebServerRequest * request ) {
// see handleBody()
case HTTP_PUT :
break ;
String url = request - > url ( ) ;
default :
request - > send ( 405 ) ;
break ;
}
}
if ( url . equals ( " /rpc " ) ) {
_onRPC ( request ) ;
return true ;
const String & pattern ( ) const {
return ApiBaseWebHandler : : pattern ( ) ;
}
}
if ( url . equals ( " /api " ) | | url . equals ( " /apis " ) ) {
_onAPIs ( request ) ;
return true ;
const PathParts & parts ( ) const {
return ApiBaseWebHandler : : parts ( ) ;
}
}
if ( ! url . startsWith ( " /api/ " ) ) return false ;
private :
ApiJsonHandler _get ;
ApiJsonHandler _put ;
} ;
// [alexa] don't call the http api -> response for alexa is done by fauxmoesp lib
# if ALEXA_SUPPORT
if ( url . indexOf ( " /lights " ) > 14 ) return false ;
# endif
// ESPurna legacy API configuration
// - ?apikey=... to authorize in GET or PUT
// - ?anything=... for input data (common key is "value")
// MUST correctly override isRequestHandlerTrivial() to allow auth with PUT
// (i.e. so that ESPAsyncWebServer parses the body and adds form-data to request params list)
class ApiBasicWebHandler final : public ApiBaseWebHandler {
public :
template < typename Path , typename Callback >
ApiBasicWebHandler ( Path & & path , Callback & & get , Callback & & put ) :
ApiBaseWebHandler ( std : : forward < Path > ( path ) ) ,
_get ( std : : forward < Callback > ( get ) ) ,
_put ( std : : forward < Callback > ( put ) )
{ }
bool isRequestHandlerTrivial ( ) override {
return false ;
}
if ( ! apiAuthenticate ( request ) ) return false ;
bool canHandle ( AsyncWebServerRequest * request ) override {
if ( ! apiEnabled ( ) ) {
return false ;
}
return _apiDispatchRequest ( url , request ) ;
if ( ! _apiAcceptsText ( request ) ) {
return false ;
}
}
switch ( request - > method ( ) ) {
case HTTP_HEAD :
case HTTP_GET :
break ;
case HTTP_PUT :
if ( ! _apiIsFormDataContent ( request ) ) {
return false ;
}
break ;
default :
return false ;
}
auto helper = ApiRequestHelper ( * request , parts ( ) ) ;
if ( helper . match ( ) ) {
_apiAttachHelper ( * request , std : : move ( helper ) ) ;
return true ;
}
return false ;
}
void handleRequest ( AsyncWebServerRequest * request ) override {
if ( ! apiAuthenticate ( request ) ) {
request - > send ( 403 ) ;
return ;
}
auto method = request - > method ( ) ;
const bool is_put = (
( ! apiRestFul ( ) | | ( HTTP_PUT = = method ) )
& & request - > hasParam ( " value " , HTTP_PUT = = method )
) ;
switch ( method ) {
case HTTP_HEAD :
request - > send ( 204 ) ;
return ;
case HTTP_GET :
case HTTP_PUT : {
auto & helper = * reinterpret_cast < ApiRequestHelper * > ( request - > _tempObject ) ;
auto apireq = helper . request ( ) ;
if ( is_put ) {
if ( ! _put ( apireq ) ) {
request - > send ( 500 ) ;
return ;
}
if ( apireq . done ( ) ) {
return ;
}
}
if ( ! _get ( apireq ) ) {
request - > send ( 500 ) ;
return ;
}
if ( ! apireq . done ( ) ) {
request - > send ( 204 ) ;
return ;
}
}
default :
request - > send ( 405 ) ;
return ;
}
}
const ApiBasicHandler & get ( ) const {
return _get ;
}
const ApiBasicHandler & put ( ) const {
return _put ;
}
const String & pattern ( ) const {
return ApiBaseWebHandler : : pattern ( ) ;
}
const PathParts & parts ( ) const {
return ApiBaseWebHandler : : parts ( ) ;
}
private :
ApiBasicHandler _get ;
ApiBasicHandler _put ;
} ;
// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------
void apiReserve ( size_t size ) {
_apis . reserve ( _apis . size ( ) + size ) ;
namespace {
std : : forward_list < ApiBaseWebHandler * > _apis ;
template < typename Handler , typename Callback >
void _apiRegister ( const String & path , Callback & & get , Callback & & put ) {
// `String` is a given, since we *do* need to construct this dynamically in sensors
auto * ptr = new Handler ( String ( F ( API_BASE_PATH ) ) + path , std : : forward < Callback > ( get ) , std : : forward < Callback > ( put ) ) ;
webServer ( ) . addHandler ( reinterpret_cast < AsyncWebHandler * > ( ptr ) ) ;
_apis . emplace_front ( ptr ) ;
}
}
void apiRegister ( const Api & api ) {
if ( api . path . length ( ) > = ( ApiPathSizeMax - strlen ( " /api/ " ) - 1ul ) ) {
return ;
}
_apis . push_back ( api ) ;
} // namespace
void apiRegister ( const String & path , ApiBasicHandler & & get , ApiBasicHandler & & put ) {
_apiRegister < ApiBasicWebHandler > ( path , std : : move ( get ) , std : : move ( put ) ) ;
}
void apiRegister ( const String & path , ApiJsonHandler & & get , ApiJsonHandler & & put ) {
_apiRegister < ApiJsonWebHandler > ( path , std : : move ( get ) , std : : move ( put ) ) ;
}
}
void apiSetup ( ) {
void apiSetup ( ) {
webRequestRegister ( _apiRequestCallback ) ;
apiRegister ( F ( " list " ) ,
[ ] ( ApiRequest & request ) {
String paths ;
for ( auto & api : _apis ) {
paths + = api - > pattern ( ) + " \r \n " ;
}
request . send ( paths ) ;
return true ;
} ,
nullptr
) ;
apiRegister ( F ( " rpc " ) ,
nullptr ,
[ ] ( ApiRequest & request ) {
if ( rpcHandleAction ( request . param ( F ( " action " ) ) ) ) {
return apiOk ( request ) ;
}
return apiError ( request ) ;
}
) ;
}
}
void apiOk ( const Api & , ApiBuffer & buffer ) {
buffer . data [ 0 ] = ' O ' ;
buffer . data [ 1 ] = ' K ' ;
buffer . data [ 2 ] = ' \0 ' ;
bool apiOk ( ApiRequest & request ) {
request . send ( F ( " OK " ) ) ;
return true ;
}
}
void apiError ( const Api & , ApiBuffer & buffer ) {
buffer . data [ 0 ] = ' - ' ;
buffer . data [ 1 ] = ' E ' ;
buffer . data [ 2 ] = ' R ' ;
buffer . data [ 3 ] = ' R ' ;
buffer . data [ 4 ] = ' O ' ;
buffer . data [ 5 ] = ' R ' ;
buffer . data [ 6 ] = ' \0 ' ;
bool apiError ( ApiRequest & request ) {
request . send ( F ( " ERROR " ) ) ;
return true ;
}
}
# endif // API_SUPPORT
# endif // API_SUPPORT