Refactored web-server

pull/193/head
cskarai 8 years ago committed by Thorsten von Eicken
parent 22664fa55f
commit 9e6d9932d3
  1. 566
      web-server/web-server.c
  2. 20
      web-server/web-server.h

@ -8,14 +8,31 @@
#include "cmd.h" #include "cmd.h"
#include "serbridge.h" #include "serbridge.h"
// the file is responsible for handling user defined web-pages
// - collects HTML files from user image, shows them on the left frame
// - handles JSON data coming from the browser
// - handles SLIP messages coming from MCU
#define WEB_CB "webCb" #define WEB_CB "webCb"
#define MAX_ARGUMENT_BUFFER_SIZE 1024
#define MAX_VARS 20 struct ArgumentBuffer
{
char argBuffer[MAX_ARGUMENT_BUFFER_SIZE];
int argBufferPtr;
int numberOfArgs;
};
static char* web_server_reasons[] = { static char* web_server_reasons[] = {
"load", "refresh", "button", "submit" "load", // readable name for RequestReason::LOAD
"refresh", // readable name for RequestReason::REFRESH
"button", // readable name for RequestReason::BUTTON
"submit" // readable name for RequestReason::SUBMIT
}; };
// this variable contains the names of the user defined pages
// this information appears at the left frame below of the built in URL-s
// format: ,"UserPage1", "/UserPage1.html", "UserPage2", "/UserPage2.html",
char * webServerPages = NULL; char * webServerPages = NULL;
char * ICACHE_FLASH_ATTR WEB_UserPages() char * ICACHE_FLASH_ATTR WEB_UserPages()
@ -23,6 +40,7 @@ char * ICACHE_FLASH_ATTR WEB_UserPages()
return webServerPages; return webServerPages;
} }
// generates the content of webServerPages variable (called at booting/web page uploading)
void ICACHE_FLASH_ATTR WEB_BrowseFiles() void ICACHE_FLASH_ATTR WEB_BrowseFiles()
{ {
char buffer[1024]; char buffer[1024];
@ -34,37 +52,35 @@ void ICACHE_FLASH_ATTR WEB_BrowseFiles()
espFsIteratorInit(userPageCtx, &it); espFsIteratorInit(userPageCtx, &it);
while( espFsIteratorNext(&it) ) while( espFsIteratorNext(&it) )
{ {
int nlen = os_strlen(it.name); int nameLen = os_strlen(it.name);
if( nlen >= 6 ) if( nameLen >= 6 )
{ {
if( os_strcmp( it.name + nlen-5, ".html" ) == 0 ) // fetch HTML files
if( os_strcmp( it.name + nameLen-5, ".html" ) == 0 )
{ {
char sh_name[17]; int slashPos = nameLen - 5;
int spos = nlen-5; // chop path and .html from the name
while( slashPos > 0 && it.name[slashPos-1] != '/' )
while( spos > 0 ) slashPos--;
{
if( it.name[spos+1] == '/' )
break;
spos--;
}
int ps = nlen-5-spos; // here we check buffer overrun
if( ps > 16 ) int maxLen = 10 + os_strlen( it.name ) + (nameLen - slashPos -5);
ps = 16; if( maxLen >= sizeof(buffer) )
os_memcpy(sh_name, it.name + spos, ps); break;
sh_name[ps] = 0;
os_strcat(buffer, ", \""); os_strcat(buffer, ", \"");
os_strcat(buffer, sh_name);
int writePos = os_strlen(buffer);
for( int i=slashPos; i < nameLen-5; i++ )
buffer[writePos++] = it.name[i];
buffer[writePos] = 0; // terminating zero
os_strcat(buffer, "\", \"/"); os_strcat(buffer, "\", \"/");
os_strcat(buffer, it.name); os_strcat(buffer, it.name);
os_strcat(buffer, "\""); os_strcat(buffer, "\"");
} }
} }
if( os_strlen(buffer) > 600 )
break;
} }
} }
@ -76,6 +92,7 @@ void ICACHE_FLASH_ATTR WEB_BrowseFiles()
os_memcpy( webServerPages, buffer, len ); os_memcpy( webServerPages, buffer, len );
} }
// initializer
void ICACHE_FLASH_ATTR WEB_Init() void ICACHE_FLASH_ATTR WEB_Init()
{ {
espFsInit(userPageCtx, (void *)getUserPageSectionStart(), ESPFS_FLASH); espFsInit(userPageCtx, (void *)getUserPageSectionStart(), ESPFS_FLASH);
@ -83,288 +100,339 @@ void ICACHE_FLASH_ATTR WEB_Init()
os_printf("Valid user file system found!\n"); os_printf("Valid user file system found!\n");
else else
os_printf("No user file system found!\n"); os_printf("No user file system found!\n");
WEB_BrowseFiles(); WEB_BrowseFiles(); // collect user defined HTML files
} }
int ICACHE_FLASH_ATTR WEB_CgiJsonHook(HttpdConnData *connData) // initializes the argument buffer
static void WEB_argInit(struct ArgumentBuffer * argBuffer)
{ {
if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up. argBuffer->numberOfArgs = 0;
argBuffer->argBufferPtr = 0;
}
void * cgiData = connData->cgiData; // adds an argument to the argument buffer (returns 0 if successful)
static int WEB_addArg(struct ArgumentBuffer * argBuffer, char * arg, int argLen )
{
if( argBuffer->argBufferPtr + argLen + sizeof(int) >= MAX_ARGUMENT_BUFFER_SIZE )
return -1; // buffer overflow
if( cgiData == NULL ) os_memcpy(argBuffer->argBuffer + argBuffer->argBufferPtr, &argLen, sizeof(int));
if( argLen != 0 )
{ {
if( !flashConfig.slip_enable ) os_memcpy( argBuffer->argBuffer + argBuffer->argBufferPtr + sizeof(int), arg, argLen );
{ argBuffer->numberOfArgs++;
errorResponse(connData, 400, "Slip processing is disabled!"); }
return HTTPD_CGI_DONE;
}
CmdCallback* cb = cmdGetCbByName( WEB_CB );
if( cb == NULL )
{
errorResponse(connData, 500, "No MCU callback is registered!");
return HTTPD_CGI_DONE;
}
if( serbridgeInProgramming() )
{
errorResponse(connData, 500, "Slip disabled at programming mode!");
return HTTPD_CGI_DONE;
}
char reasonBuf[16]; argBuffer->argBufferPtr += argLen + sizeof(int);
int i; return 0;
int len = httpdFindArg(connData->getArgs, "reason", reasonBuf, sizeof(reasonBuf)); }
if( len < 0 )
{
errorResponse(connData, 400, "No reason specified!");
return HTTPD_CGI_DONE;
}
RequestReason reason = INVALID; // creates and sends a SLIP message from the argument buffer
for(i=0; i < sizeof(web_server_reasons)/sizeof(char *); i++) static void WEB_sendArgBuffer(struct ArgumentBuffer * argBuffer, HttpdConnData *connData, int id, RequestReason reason)
{ {
if( os_strcmp( web_server_reasons[i], reasonBuf ) == 0 ) cmdResponseStart(CMD_WEB_REQ_CB, id, 4 + argBuffer->numberOfArgs);
reason = (RequestReason)i; uint16_t r = (uint16_t)reason;
} cmdResponseBody(&r, sizeof(uint16_t)); // 1st argument: reason
cmdResponseBody(&connData->conn->proto.tcp->remote_ip, 4); // 2nd argument: IP
cmdResponseBody(&connData->conn->proto.tcp->remote_port, sizeof(uint16_t)); // 3rd argument: port
cmdResponseBody(connData->url, os_strlen(connData->url)); // 4th argument: URL
int p = 0;
for( int j=0; j < argBuffer->numberOfArgs; j++ )
{
int argLen;
os_memcpy( &argLen, argBuffer->argBuffer + p, sizeof(int) );
if( reason == INVALID ) char * arg = argBuffer->argBuffer + p + sizeof(int);
{ cmdResponseBody(arg, argLen);
errorResponse(connData, 400, "Invalid reason!"); p += argLen + sizeof(int);
}
cmdResponseEnd();
}
// this method processes SLIP data from MCU and converts to JSON
// this method receives JSON from the browser, sends SLIP data to MCU
static int ICACHE_FLASH_ATTR WEB_handleJSONRequest(HttpdConnData *connData)
{
if( !flashConfig.slip_enable )
{
errorResponse(connData, 400, "Slip processing is disabled!");
return HTTPD_CGI_DONE;
}
CmdCallback* cb = cmdGetCbByName( WEB_CB );
if( cb == NULL )
{
errorResponse(connData, 500, "No MCU callback is registered!");
return HTTPD_CGI_DONE;
}
if( serbridgeInProgramming() )
{
errorResponse(connData, 500, "Slip disabled at uploading program onto the MCU!");
return HTTPD_CGI_DONE;
}
char reasonBuf[16];
int i;
int len = httpdFindArg(connData->getArgs, "reason", reasonBuf, sizeof(reasonBuf));
if( len < 0 )
{
errorResponse(connData, 400, "No reason specified!");
return HTTPD_CGI_DONE; return HTTPD_CGI_DONE;
} }
char body[1024]; RequestReason reason = INVALID;
int bodyPtr = 0; for(i=0; i < sizeof(web_server_reasons)/sizeof(char *); i++)
int argNum = 0; {
char *argPos[MAX_VARS]; if( os_strcmp( web_server_reasons[i], reasonBuf ) == 0 )
int argLen[MAX_VARS]; reason = (RequestReason)i;
}
switch(reason) if( reason == INVALID )
{ {
case BUTTON: errorResponse(connData, 400, "Invalid reason!");
argLen[0] = httpdFindArg(connData->getArgs, "id", body, sizeof(body)); return HTTPD_CGI_DONE;
if( argLen[0] <= 0 ) }
struct ArgumentBuffer argBuffer;
WEB_argInit( &argBuffer );
switch(reason)
{
case BUTTON:
{
char id_buf[40];
int id_len = httpdFindArg(connData->getArgs, "id", id_buf, sizeof(id_buf));
if( id_len <= 0 )
{ {
errorResponse(connData, 400, "No button ID specified!"); errorResponse(connData, 400, "No button ID specified!");
return HTTPD_CGI_DONE; return HTTPD_CGI_DONE;
} }
argPos[0] = body; if( WEB_addArg(&argBuffer, id_buf, id_len) )
argNum++;
break;
case SUBMIT:
{ {
if( connData->post->received < connData->post->len ) errorResponse(connData, 400, "Post too large!");
return HTTPD_CGI_DONE;
}
}
break;
case SUBMIT:
{
if( connData->post->received < connData->post->len )
{
errorResponse(connData, 400, "Post too large!");
return HTTPD_CGI_DONE;
}
int bptr = 0;
while( bptr < connData->post->len )
{
char * line = connData->post->buff + bptr;
char * eo = os_strchr(line, '&' );
if( eo != NULL )
{
*eo = 0;
bptr = eo - connData->post->buff + 1;
}
else
{ {
errorResponse(connData, 400, "Post too large!"); eo = line + os_strlen( line );
return HTTPD_CGI_DONE; bptr = connData->post->len;
} }
int bptr = 0; int len = os_strlen(line);
while( len >= 1 && ( line[len-1] == '\r' || line[len-1] == '\n' ))
len--;
line[len] = 0;
while( bptr < connData->post->len ) char * val = os_strchr(line, '=');
if( val != NULL )
{ {
if( argNum >= MAX_VARS ) *val = 0;
char * name = line;
int vblen = os_strlen(val+1) * 2;
char value[vblen];
httpdUrlDecode(val+1, strlen(val+1), value, vblen);
int namLen = os_strlen(name);
int valLen = os_strlen(value);
char arg[namLen + valLen + 3];
int argPtr = 0;
arg[argPtr++] = (char)WEB_STRING;
os_strcpy( arg + argPtr, name );
argPtr += namLen;
arg[argPtr++] = 0;
os_strcpy( arg + argPtr, value );
argPtr += valLen;
if( WEB_addArg(&argBuffer, arg, argPtr) )
{ {
errorResponse(connData, 400, "Too many variables!"); errorResponse(connData, 400, "Post too large!");
return HTTPD_CGI_DONE; return HTTPD_CGI_DONE;
} }
}
}
}
break;
case LOAD:
case REFRESH:
default:
break;
}
char * line = connData->post->buff + bptr; if( WEB_addArg(&argBuffer, NULL, 0) )
{
char * eo = os_strchr(line, '&' ); errorResponse(connData, 400, "Post too large!");
if( eo != NULL ) return HTTPD_CGI_DONE;
{ }
*eo = 0;
bptr = eo - connData->post->buff + 1;
}
else
{
eo = line + os_strlen( line );
bptr = connData->post->len;
}
int len = os_strlen(line);
while( len >= 1 && ( line[len-1] == '\r' || line[len-1] == '\n' ))
len--;
line[len] = 0;
char * val = os_strchr(line, '='); os_printf("Web callback to MCU: %s\n", reasonBuf);
if( val != NULL )
{
*val = 0;
char * name = line;
int vblen = os_strlen(val+1) * 2;
char value[vblen];
httpdUrlDecode(val+1, strlen(val+1), value, vblen);
int namLen = os_strlen(name); WEB_sendArgBuffer(&argBuffer, connData, (uint32_t)cb->callback, reason );
int valLen = os_strlen(value);
int totallen = namLen + valLen + 2; if( reason == SUBMIT )
if( bodyPtr + totallen > sizeof(body) - 10 ) {
{ httpdStartResponse(connData, 204);
errorResponse(connData, 400, "Post too large!"); httpdEndHeaders(connData);
return HTTPD_CGI_DONE; return HTTPD_CGI_DONE;
} }
argPos[argNum] = body + bodyPtr; return HTTPD_CGI_MORE;
}
body[bodyPtr++] = (char)WEB_STRING; // this method receives SLIP data from MCU sends JSON to the browser
os_strcpy( body + bodyPtr, name ); static int ICACHE_FLASH_ATTR WEB_handleMCUResponse(HttpdConnData *connData, CmdRequest * response)
bodyPtr += namLen; {
body[bodyPtr++] = 0; char jsonBuf[1500];
int jsonPtr = 0;
os_strcpy( body + bodyPtr, value );
bodyPtr += valLen;
argLen[argNum++] = totallen; jsonBuf[jsonPtr++] = '{';
}
}
}
break;
case LOAD:
case REFRESH:
default:
break;
}
os_printf("Web callback to MCU: %s\n", reasonBuf); int c = 2;
while( c++ < cmdGetArgc(response) )
{
int len = cmdArgLen(response);
char buf[len+1];
buf[len] = 0;
cmdResponseStart(CMD_WEB_REQ_CB, (uint32_t)cb->callback, 4 + argNum); cmdPopArg(response, buf, len);
uint16_t r = (uint16_t)reason;
cmdResponseBody(&r, sizeof(uint16_t));
cmdResponseBody(&connData->conn->proto.tcp->remote_ip, 4);
cmdResponseBody(&connData->conn->proto.tcp->remote_port, sizeof(uint16_t));
cmdResponseBody(connData->url, os_strlen(connData->url));
int j; if(len == 0)
for( j=0; j < argNum; j++ ) break; // last argument
cmdResponseBody(argPos[j], argLen[j]);
cmdResponseEnd(); if( c > 3 ) // skip the first argument
jsonBuf[jsonPtr++] = ',';
if( reason == SUBMIT ) if( jsonPtr + 20 + len > sizeof(jsonBuf) )
{ {
httpdStartResponse(connData, 204); errorResponse(connData, 500, "Response too large!");
httpdEndHeaders(connData);
return HTTPD_CGI_DONE; return HTTPD_CGI_DONE;
} }
connData->cgiData = (void *)1; WebValueType type = (WebValueType)buf[0];
}
if( connData->cgiResponse != NULL ) // data from MCU
{
char jsonBuf[1500];
int jsonPtr = 0;
int nameLen = os_strlen(buf+1);
jsonBuf[jsonPtr++] = '"';
os_memcpy(jsonBuf + jsonPtr, buf + 1, nameLen);
jsonPtr += nameLen;
jsonBuf[jsonPtr++] = '"';
jsonBuf[jsonPtr++] = ':';
jsonBuf[jsonPtr++] = '{'; char * value = buf + 2 + nameLen;
CmdRequest * req = (CmdRequest *)(connData->cgiResponse);
int c = 2; switch(type)
while( c++ < cmdGetArgc(req) )
{ {
int len = cmdArgLen(req); case WEB_NULL:
char buf[len+1]; os_memcpy(jsonBuf + jsonPtr, "null", 4);
buf[len] = 0; jsonPtr += 4;
break;
cmdPopArg(req, buf, len); case WEB_INTEGER:
{
if(len == 0) int v;
break; // last argument os_memcpy( &v, value, 4);
if( c > 3 ) // skip the first argument
jsonBuf[jsonPtr++] = ',';
if( jsonPtr + 20 + len > sizeof(jsonBuf) )
{
errorResponse(connData, 500, "Response too large!");
return HTTPD_CGI_DONE;
}
WebValueType type = (WebValueType)buf[0];
int nameLen = os_strlen(buf+1); char intbuf[20];
jsonBuf[jsonPtr++] = '"'; os_sprintf(intbuf, "%d", v);
os_memcpy(jsonBuf + jsonPtr, buf + 1, nameLen); os_strcpy(jsonBuf + jsonPtr, intbuf);
jsonPtr += nameLen; jsonPtr += os_strlen(intbuf);
jsonBuf[jsonPtr++] = '"'; }
jsonBuf[jsonPtr++] = ':'; break;
case WEB_BOOLEAN:
if( *value ) {
os_memcpy(jsonBuf + jsonPtr, "true", 4);
jsonPtr += 4;
} else {
os_memcpy(jsonBuf + jsonPtr, "false", 5);
jsonPtr += 5;
}
break;
case WEB_FLOAT:
{
float f;
os_memcpy( &f, value, 4);
char * value = buf + 2 + nameLen; char intbuf[20];
os_sprintf(intbuf, "%f", f);
os_strcpy(jsonBuf + jsonPtr, intbuf);
jsonPtr += os_strlen(intbuf);
}
break;
case WEB_STRING:
jsonBuf[jsonPtr++] = '"';
while(*value)
{
if( *value == '\\' || *value == '"' )
jsonBuf[jsonPtr++] = '\\';
jsonBuf[jsonPtr++] = *(value++);
}
jsonBuf[jsonPtr++] = '"';
break;
case WEB_JSON:
os_memcpy(jsonBuf + jsonPtr, value, len - 2 - nameLen);
jsonPtr += len - 2 - nameLen;
break;
}
}
switch(type) jsonBuf[jsonPtr++] = '}';
{
case WEB_NULL:
os_memcpy(jsonBuf + jsonPtr, "null", 4);
jsonPtr += 4;
break;
case WEB_INTEGER:
{
int v;
os_memcpy( &v, value, 4);
char intbuf[20]; noCacheHeaders(connData, 200);
os_sprintf(intbuf, "%d", v); httpdHeader(connData, "Content-Type", "application/json");
os_strcpy(jsonBuf + jsonPtr, intbuf);
jsonPtr += os_strlen(intbuf);
}
break;
case WEB_BOOLEAN:
if( *value ) {
os_memcpy(jsonBuf + jsonPtr, "true", 4);
jsonPtr += 4;
} else {
os_memcpy(jsonBuf + jsonPtr, "false", 5);
jsonPtr += 5;
}
break;
case WEB_FLOAT:
{
float f;
os_memcpy( &f, value, 4);
char intbuf[20]; char cl[16];
os_sprintf(intbuf, "%f", f); os_sprintf(cl, "%d", jsonPtr);
os_strcpy(jsonBuf + jsonPtr, intbuf); httpdHeader(connData, "Content-Length", cl);
jsonPtr += os_strlen(intbuf); httpdEndHeaders(connData);
}
break;
case WEB_STRING:
jsonBuf[jsonPtr++] = '"';
while(*value)
{
if( *value == '\\' || *value == '"' )
jsonBuf[jsonPtr++] = '\\';
jsonBuf[jsonPtr++] = *(value++);
}
jsonBuf[jsonPtr++] = '"';
break;
case WEB_JSON:
os_memcpy(jsonBuf + jsonPtr, value, len - 2 - nameLen);
jsonPtr += len - 2 - nameLen;
break;
}
}
jsonBuf[jsonPtr++] = '}'; httpdSend(connData, jsonBuf, jsonPtr);
return HTTPD_CGI_DONE;
}
noCacheHeaders(connData, 200); // this method is responsible for the MCU <==JSON==> Browser communication
httpdHeader(connData, "Content-Type", "application/json"); int ICACHE_FLASH_ATTR WEB_CgiJsonHook(HttpdConnData *connData)
{
if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up.
char cl[16]; void * cgiData = connData->cgiData;
os_sprintf(cl, "%d", jsonPtr);
httpdHeader(connData, "Content-Length", cl);
httpdEndHeaders(connData);
httpdSend(connData, jsonBuf, jsonPtr); if( cgiData == NULL )
return HTTPD_CGI_DONE; {
connData->cgiData = (void *)1; // indicate, that request was processed
return WEB_handleJSONRequest(connData);
} }
if( connData->cgiResponse != NULL ) // data from MCU
return WEB_handleMCUResponse(connData, (CmdRequest *)(connData->cgiResponse));
return HTTPD_CGI_MORE; return HTTPD_CGI_MORE;
} }
// this method is called when MCU transmits WEB_DATA command
void ICACHE_FLASH_ATTR WEB_Data(CmdPacket *cmd) void ICACHE_FLASH_ATTR WEB_Data(CmdPacket *cmd)
{ {
CmdRequest req; CmdRequest req;
@ -373,14 +441,14 @@ void ICACHE_FLASH_ATTR WEB_Data(CmdPacket *cmd)
if (cmdGetArgc(&req) < 2) return; if (cmdGetArgc(&req) < 2) return;
uint8_t ip[4]; uint8_t ip[4];
cmdPopArg(&req, ip, 4); cmdPopArg(&req, ip, 4); // pop the IP address
uint16_t port; uint16_t port;
cmdPopArg(&req, &port, 2); cmdPopArg(&req, &port, 2); // pop the HTTP port
HttpdConnData * conn = httpdLookUpConn(ip, port); HttpdConnData * conn = httpdLookUpConn(ip, port); // look up connection based on IP/port
if( conn != NULL && conn->cgi == WEB_CgiJsonHook ) // make sure that the right CGI handler will be called if( conn != NULL && conn->cgi == WEB_CgiJsonHook ) // make sure that the right CGI handler is configured
httpdSetCGIResponse( conn, &req ); httpdSetCGIResponse( conn, &req );
else else
os_printf("WEB response ignored as no valid http connection found for the request!\n"); os_printf("WEB_DATA ignored as no valid HTTP connection found!\n");
} }

@ -8,22 +8,22 @@
typedef enum typedef enum
{ {
LOAD=0, LOAD=0, // loading web-page content at the first time
REFRESH, REFRESH, // loading web-page subsequently
BUTTON, BUTTON, // HTML button pressed
SUBMIT, SUBMIT, // HTML form is submitted
INVALID=-1, INVALID=-1,
} RequestReason; } RequestReason;
typedef enum typedef enum
{ {
WEB_STRING=0, WEB_STRING=0, // the value is string
WEB_NULL, WEB_NULL, // the value is NULL
WEB_INTEGER, WEB_INTEGER, // the value is integer
WEB_BOOLEAN, WEB_BOOLEAN, // the value is boolean
WEB_FLOAT, WEB_FLOAT, // the value is float
WEB_JSON WEB_JSON // the value is JSON data
} WebValueType; } WebValueType;
void WEB_Init(); void WEB_Init();

Loading…
Cancel
Save