Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
Dependents: Smart-WiFly-WebServer WattEye X10Svr SSDP_Server
SW_HTTPServer.cpp
- Committer:
- WiredHome
- Date:
- 2013-09-01
- Revision:
- 16:6ebacf2946d8
- Parent:
- 14:19c5f6151319
- Child:
- 17:69ff00ce39f4
File content as of revision 16:6ebacf2946d8:
//
// @note Copyright © 2013 by Smartware Computing, all rights reserved.
// Individuals may use this application for evaluation or non-commercial
// purposes. Within this restriction, changes may be made to this application
// as long as this copyright notice is retained. The user shall make
// clear that their work is a derived work, and not the original.
// Users of this application and sources accept this application "as is" and
// shall hold harmless Smartware Computing, for any undesired results while
// using this application - whether real or imagined.
//
// author David Smart, Smartware Computing
//
#include "mbed.h"
#include "SW_HTTPServer.h"
#include "Utility.h"
#define DEBUG
const char * DEFAULT_FILENAME = "index.htm";
// Header information to always send (must be \r\n terminated)
const char hdr_httpver[] = "HTTP/1.0"; // typically HTTP/1.0 or HTTP/1.1
const char hdr_age[] = "Max-age: 0\r\n"; // expires right away
const char hdr_server[] = "Server: Smart_Server v0.1\r\n"; // Server
const char hdr_dnt[] = "DNT: 1\r\n"; // Do Not Track
const char hdr_close[] = "Connection: close\r\n"; // tell the client to close the connection
const char nl[] = "\r\n"; // final \r\n for the termination of the header
static const struct {
char *ext;
char *filetype;
} extensions [] = {
{".gif", "Content-Type: image/gif\r\n" },
{".jpg", "Content-Type: image/jpeg\r\n" },
{".jpeg","Content-Type: image/jpeg\r\n" },
{".ico", "Content-Type: image/x-icon\r\n" },
{".png", "Content-Type: image/png\r\n" },
{".zip", "Content-Type: image/zip\r\n" },
{".gz", "Content-Type: image/gz\r\n" },
{".tar", "Content-Type: image/tar\r\n" },
{".txt", "Content-Type: plain/text\r\n" },
{".pdf", "Content-Type: application/pdf\r\n" },
{".htm", "Content-Type: text/html\r\n" },
{".html","Content-Type: text/html\r\n" },
{0,0}
};
#ifdef DEBUG
// This uses standard library dynamic memory management, but for an
// embedded system there are alternates that may make better sense -
// search the web for embedded system malloc alternates.
static void * MyMalloc(int x, int y)
{
std::printf("[%04d] malloc(%d)\r\n", y, x);
return malloc(x);
}
static char toP(void * x)
{
char * c = (char *) x;
if (*c >= ' ' && *c < 0x7F)
return *c;
else
return '.';
}
#define mymalloc(x) MyMalloc(x, __LINE__)
#define myfree(x) \
pc->printf("[%4d] free(%02x %02x %02x %02x %02x ... %c%c%c%c%c)\r\n", __LINE__, \
*x, *(x+1), *(x+2), *(x+3), *(x+4), \
toP(x), toP(x+1), toP(x+2), toP(x+3), toP(x+4) ); \
free(x);
#else
#define mymalloc(x) malloc(x)
#define myfree(x) free(x)
#endif
HTTPServer::HTTPServer(
Wifly * _wf,
int port,
const char * _webroot,
int maxheaderParams,
int _maxqueryParams,
int _maxdynamicpages,
PC * _pc,
int _allocforheader,
int _allocforfile)
{
wifly = _wf;
webroot = (char *)malloc(strlen(_webroot)+1);
strcpy(webroot, _webroot);
maxqueryParams = _maxqueryParams;
maxdynamicpages = _maxdynamicpages;
headerParams = (namevalue *)malloc(maxheaderParams * sizeof(namevalue));
queryParams = (namevalue *)malloc(maxqueryParams * sizeof(namevalue));
handlers = (handler *)malloc(maxdynamicpages * sizeof(handler));
headerbuffersize = _allocforheader;
headerbuffer = (char *)malloc(headerbuffersize);
pc = _pc;
queryType = NULL;
queryString = NULL;
postQueryString = NULL;
queryParamCount = 0;
handlercount = 0;
maxheaderbytes = 0;
server = new TCPSocketServer();
server->bind(port);
server->listen();
server->set_blocking(false, 10);
ResetPerformanceData();
PerformanceTimer.start();
}
HTTPServer::~HTTPServer()
{
int i;
for (i=0; i<handlercount; i++)
myfree(handlers[i].path);
myfree(headerbuffer);
myfree(handlers);
myfree(queryParams);
myfree(webroot);
webroot = NULL;
}
int HTTPServer::GetMaxHeaderSize()
{
return maxheaderbytes;
}
bool HTTPServer::RegisterHandler(const char * path, Handler callback)
{
if (handlercount < maxdynamicpages && path && callback) {
handlers[handlercount].path = (char *)mymalloc(strlen(path)+1);
memcpy(handlers[handlercount].path, path, strlen(path)+1);
handlers[handlercount].callback = callback;
handlercount++;
return true;
} else {
return false;
}
}
// Poll()
//
// *OPEN*GET /x=1 HTTP/1.1
// Host: 192.168.1.140
// Connection: keep-alive
// Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
// User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.94 Safari/537.36
// Accept-Encoding: gzip,deflate,sdch
// Accept-Language: en-US,en;q=0.8
//
void HTTPServer::Poll()
{
typedef enum {
Idle, // waiting for a connection
Receiving, // receiving data
Sending, // send the response
WaitingToClose, // small timeout to close
Reset
} state;
static state op = Idle;
static char * bPtr = headerbuffer;
int n;
static unsigned int t_ref; // reference point for the PerformanceTimer
#ifdef DEBUG
static state lastOp = Reset;
if (lastOp != op) {
const char *states[] = {"Idle", "Receiving", "Sending", "WaitingToClose", "Reset"};
pc->printf("Poll: %s\r\n", states[op]);
lastOp = op;
}
#endif
switch(op) {
default: // not expected to arrive here
op = Idle;
break;
case Idle:
PerformanceTimer.reset();
bPtr = headerbuffer;
if (0 == server->accept(client)) {
op = Receiving;
t_ref = (unsigned int)PerformanceTimer.read_us();
#ifdef DEBUG
pc->printf("Accept at %u\r\n", t_ref);
#endif
}
break;
case Receiving:
n = client.receive(bPtr, headerbuffersize - (bPtr - headerbuffer));
if (n < 0) {
#ifdef DEBUG
pc->printf("*** client.receive() => %d\r\n", n);
#endif
} else if (n) {
bPtr[n] = '\0';
if (ParseHeader(headerbuffer)) {
op = Sending;
t_ref = RecordPerformanceData(&perfData.Header, t_ref);
}
bPtr += n;
}
break;
case Sending:
SendResponse();
op = WaitingToClose;
RecordPerformanceData(&perfData.SendData, t_ref);
break;
case WaitingToClose:
close_connection();
op = Idle;
break;
}
}
const char * HTTPServer::GetSupportedType(const char * filename)
{
int i;
int buflen = strlen(filename);
int extlen;
for (i=0; extensions[i].ext != 0; i++) {
extlen = strlen(extensions[i].ext);
if ( !strncmp(&filename[buflen-extlen], extensions[i].ext, extlen)) {
return extensions[i].filetype;
}
}
return NULL;
}
void HTTPServer::send(const char * msg, int bytes)
{
if (bytes == -1)
bytes = strlen(msg);
wifly->send(msg, bytes);
}
bool HTTPServer::SendFile(const char * filename, const char * filetype)
{
FILE * fp;
fp = fopen(filename,"rb");
if (fp) { // can open it
char *fbuffer = (char *)mymalloc(FILESEND_BUF_SIZE);
int bytes;
if (fbuffer) {
header(200, "OK", filetype);
bytes = fread(fbuffer,sizeof(char),FILESEND_BUF_SIZE,fp);
while (bytes > 0) {
send(fbuffer, bytes);
bytes = fread(fbuffer,sizeof(char),FILESEND_BUF_SIZE,fp);
}
myfree(fbuffer);
} else {
header(500, "Server Error", "Pragma: err - insufficient memory\r\n");
}
fclose(fp);
return true;
} else {
header(404, "Not Found", "Pragma: err - Can't open file\r\n");
return false;
}
}
int HTTPServer::HexCharToInt(char c)
{
if (c >= 'a' && c <= 'f')
return (c - 'a' + 10);
else if (c >= 'A' && c <= 'F')
return (c - 'A' + 10);
else if (c >= '0' && c <= '9')
return c - '0';
else
return 0;
}
char HTTPServer::HexPairToChar(char * p)
{
return 16 * HexCharToInt(*p) + HexCharToInt(*(p+1));
}
void HTTPServer::UnescapeString(char * encoded)
{
char *p;
// first convert '+' to ' '
p = strchr(encoded, '+');
while (p) {
*p = ' ';
p = strchr(encoded, '+');
}
// then convert hex '%xx' to char 'x'
p = strchr(encoded, '%');
while (p) {
if (strchr("0123456789ABCDEFabcdef", *(p+1))
&& strchr("0123456789ABCDEFabcdef", *(p+2)) ) {
*p = HexPairToChar(p+1);
p++; // advance past the %
char * a = p;
char * b = p + 2;
do {
*a++ = *b++;
} while (*b);
*a = '\0';
}
p = strchr(p, '%');
}
}
const char * HTTPServer::GetParameter(const char * name)
{
for (int i=0; i<queryParamCount; i++) {
if (strcmp(queryParams[i].name, name) == 0) {
return queryParams[i].value;
}
}
return NULL;
}
// this=that&who=what&more=stuff...
// ^ ^ ^
void HTTPServer::ParseParameters(char * pName)
{
char * pVal;
char * pNextName;
// Parse queryParams
pVal = strchr(pName, '#'); // If there is a '#fragment_id', we can ignore it
if (pVal)
*pVal = '\0';
do {
queryParams[queryParamCount].name = pName;
pVal = strchr(pName, '=');
pNextName = strchr(pName,'&');
if (pVal) {
if (pNextName == NULL || (pNextName && pNextName > pVal)) {
*pVal++ = '\0';
queryParams[queryParamCount].value = pVal;
pName = pVal;
}
}
queryParamCount++;
if (pNextName) {
pName = pNextName;
*pName++ = '\0';
} else {
pName = NULL;
}
} while (pName && queryParamCount < maxqueryParams);
}
bool HTTPServer::GetRemoteAddr(char * str, int strSize)
{
bool res = false;
char *p;
if (strSize < 16) { // Can only guard it here w/o modifying Wifly class
*str = '\0';
return res;
}
res = wifly->sendCommand("show z\r", NULL, str, strSize);
if (res) {
p = strchr(str, '\n'); // truncate after the octets.
if (p) *p = '\0';
p = strchr(str, ' '); // or a space
if (p) *p = '\0';
p = strchr(str, '<'); // or a <
if (p) *p = '\0';
res = true;
}
wifly->exit();
return res;
}
void HTTPServer::header(int code, const char * code_text, const char * content_type, const char * optional_text)
{
char http[100];
sprintf(http, "%s %i %s\r\n", hdr_httpver, code, code_text);
send(http);
send(hdr_age);
send(hdr_server);
if (content_type) {
send(content_type);
}
if (optional_text) {
send(optional_text);
}
send(hdr_dnt);
send(hdr_close);
send(nl);
}
bool HTTPServer::close_connection()
{
bool res;
res = server->close();
#ifdef DEBUG
pc->printf("close connection returned %d\r\n", res);
#endif
return res;
}
bool HTTPServer::Extract(char * haystack, char * needle, char ** string)
{
bool ret = false; // assume failure until proven otherwise
char * qs = NULL;
char * eqs = NULL;
char * container = NULL;
char * get = strstr(haystack, needle); // what if not at the front?
if (get) {
// Seems to be a valid "...GET /QueryString HTTP/1.1"
// or "...<needle>param..."
qs = get + strlen(needle); // in case the needle didn't have space delimiters
while (*qs == ' ')
qs++;
// /QueryString\0HTTP/1.1\0\0
if (*string) // recycle old string when working a new one
myfree(*string);
container = (char *)mymalloc(strlen(qs));
if (container) {
strcpy(container, qs);
eqs = strchr(container, ' ');
if (eqs)
*eqs = '\0';
*string = container;
#ifdef DEBUG
pc->printf("Extract(%s) = %s\r\n", needle, container);
#endif
ret = true;
} else {
*string = NULL; // something bad happened... no memory
}
}
return ret;
}
char * HTTPServer::rewriteWithDefaultFile(char * queryString)
{
char * temp = (char *)mymalloc(strlen(queryString) + strlen(DEFAULT_FILENAME) + 1);
if (temp) {
*temp = '\0';
strcpy(temp, queryString);
strcat(temp, DEFAULT_FILENAME);
myfree(queryString);
return temp;
} else {
return queryString;
}
}
char * HTTPServer::rewritePrependWebroot(char * queryString)
{
char * temp = (char *)mymalloc(strlen(webroot) + strlen(queryString) + 1);
if (temp) {
*temp = '\0';
strcpy(temp, webroot);
if (temp[strlen(temp)-1] == '/' && *queryString == '/')
temp[strlen(temp)-1] = '\0';
strcat(temp, queryString);
myfree(queryString);
return temp;
} else {
return queryString;
}
}
bool HTTPServer::CheckDynamicHandlers()
{
bool regHandled = false;
// If this queryString is in the list of registered handlers, call that
for (int i=0; i<handlercount; i++) {
if (strcmp(handlers[i].path, queryString) == 0) {
(*handlers[i].callback)(this, SEND_PAGE, queryString, queryParams, queryParamCount);
regHandled = true;
break; // we only execute the first one
}
}
return regHandled;
}
void HTTPServer::SendResponse()
{
#ifdef DEBUG
pc->printf("SendResponse(%s) [%d]\r\n", queryType, __LINE__);
#endif
if (strcmp(queryType, "GET") == 0 || strcmp(queryType, "POST") == 0) {
if (!(queryString[0] == '.' && queryString[1] == '.')) {
const char * fType;
#ifdef DEBUG
pc->printf(" SendResponse() [%d]\r\n", __LINE__);
#endif
if (!CheckDynamicHandlers()) {
// Otherwise, this queryString must be trying to reference a static file
if (queryString[strlen(queryString)-1] == '/') {
queryString = rewriteWithDefaultFile(queryString);
}
// see if we support this file type
fType = GetSupportedType(queryString);
if (fType) {
queryString = rewritePrependWebroot(queryString);
SendFile(queryString, fType);
} else {
//pc->printf("Unsupported file type %s\r\n", queryString);
header(404, "Not Found", "Pragma: err - Unsupported type\r\n");
}
}
} else {
//pc->printf("Unsupported path %s\r\n", queryString);
header(400, "Bad Request", "Pragma: err - Unsupported path\r\n");
}
} else {
//pc->printf("Unsupported query type %s\r\n", queryType);
header(400, "Bad Request", "Pragma: err - Unsupported query type\r\n");
}
if (queryType) {
myfree(queryType);
queryType = NULL;
}
if (queryString) {
myfree(queryString);
queryString = NULL;
}
if (postQueryString) {
myfree(postQueryString);
postQueryString = NULL;
}
}
bool HTTPServer::ParseHeader(char * buffer)
{
char * dblCR;
bool advanceState = false;
int bytecount;
// Buffer could have partial, but the double \r\n is the key
// GET /QueryString?this=that&sky=blue HTTP/1.1\r\n
// GET /QueryString HTTP/1.1\r\nHost: 192.168.1.140\r\nCache-Con
// GET /QueryString HTTP/1.1\r\nHost: 192.168.1.140\r\nCache-Control: max-age=0\r\n\r\n
dblCR = strstr(buffer,"\r\n\r\n");
if (dblCR) { // Have to scan from the beginning in case split on \r
#ifdef DEBUG
pc->printf("==\r\n%s==\r\n", buffer);
#endif
char * soRec = buffer; // start of the next record of text
char * eoRec = strchr(soRec, '\n'); // search for end of the current record
headerParamCount = 0;
bytecount = strlen(buffer);
if (bytecount > maxheaderbytes)
maxheaderbytes = bytecount;
while (eoRec) {
*eoRec = '\0';
if (*(eoRec-1) == '\r')
*(eoRec-1) = '\0';
// Inspect the supported query types (GET, POST) and ignore (HEAD, PUT, OPTION, DELETE, TRACE, CONNECT]
// This is very clumsy
if (strstr(soRec, "GET ") == soRec) {
Extract(soRec, "GET", &queryString);
if (queryString) {
queryType = (char *)mymalloc(strlen("GET")+1);
strcpy(queryType, "GET");
}
} else if (strstr(soRec, "POST ") == soRec) {
Extract(soRec, "POST", &queryString);
if (queryString) {
queryType = (char *)mymalloc(strlen("POST")+1);
strcpy(queryType, "POST");
}
}
// if there is a ": " delimiter, we have a header item to parse into name,value pair
// "Connection: keep-alive" becomes "Connection" "keep-alive"
char *delim = strstr(soRec, ": ");
char *chkSpace = strchr(soRec, ' '); // a field-name has no space ahead of the ":"
if (delim
&& (!chkSpace || (chkSpace && delim < chkSpace))
&& headerParamCount < maxheaderParams) {
*delim++ = '\0'; // replace ": " with null
*delim++ = '\0';
headerParams[headerParamCount].name = soRec;
headerParams[headerParamCount].value = delim;
#ifdef DEBUG
pc->printf("%d: headerParams[%s] = {%s}\r\n", headerParamCount,
headerParams[headerParamCount].name, headerParams[headerParamCount].value);
#endif
headerParamCount++;
}
soRec = eoRec + 1;
eoRec = strchr(soRec, '\n');
}
if (queryString) {
// We have enough to try to reply
#ifdef DEBUG
pc->printf("create reply queryType{%s}, queryString{%s}\r\n", "GET", queryString);
#endif
// parse queryParams - if any
// /file.htm?name1=value1&name2=value2...
// /file.htm?name1&name2=value2...
queryParamCount = 0;
char * paramDelim = strchr(queryString, '?');
if (paramDelim) {
*paramDelim++ = '\0';
UnescapeString(paramDelim); // everything after the '?'
ParseParameters(paramDelim); // pointing at the NULL, but there are queryParams beyond
}
} else {
pc->printf("ERROR: queryString not found in (%s)\r\n", soRec);
}
advanceState = true;
buffer[0] = 0;
// This part parses the extra data on a POST method.
// Since there has to be a dynamic handler registered for this
// it would make sense to move some of this responsibility to
// that handler. It could then choose if it wanted to allocate
// the requested 'Content-Length' amount of memory.
// Should we check the 'Content-Type' to see if it is
// 'application/x-www-form-urlencoded'?
int postBytes = atoi(GetHeaderValue("Content-Length"));
bool acceptIt = false;
if (strcmp(queryType, "POST") == 0 && postBytes > 0 ) {
if (postBytes) {
bool regHandled = false;
// Registered Dynamic Handler
// Callback and ask if they want to accept this data
for (int i=0; i<handlercount; i++) {
if (strcmp(handlers[i].path, queryString) == 0) {
acceptIt = (*handlers[i].callback)(this, CONTENT_LENGTH_REQUEST, queryString, queryParams, queryParamCount);
regHandled = true;
break; // we only execute the first one
}
}
if (regHandled && acceptIt) {
// If so, we'll make space for it
postQueryString = (char *)mymalloc(postBytes + 1);
if (postQueryString) {
char * offset;
int len;
dblCR += 4; // If we slurped up any of the POST,
while (*dblCR && *dblCR <= ' ')
dblCR++;
strcpy(postQueryString, dblCR); // copy that in and then get the rest
while ((len = strlen(postQueryString)) < postBytes) {
int n;
offset = postQueryString + len;
n = client.receive(offset, postBytes - len);
if (n >=0) {
offset[n] = '\0';
}
}
if (len >= 0) {
UnescapeString(postQueryString);
ParseParameters(postQueryString);
}
}
} else {
// Simply copy it to the bitbucket
int bytesToDump = postBytes;
char * bitbucket = (char *)mymalloc(201);
dblCR += 4;
while (*dblCR && *dblCR <= ' ')
dblCR++;
bytesToDump -= strlen(dblCR);
while (bytesToDump > 0) {
int n = (bytesToDump > 200) ? 200 : bytesToDump;
n = client.receive(bitbucket, n);
bytesToDump -= n;
}
myfree(bitbucket);
}
}
}
}
return advanceState;
}
const char * HTTPServer::GetHeaderValue(const char * hdr)
{
int i;
for (i=0; i<headerParamCount; i++)
{
if (strcmp(hdr, headerParams[i].name) == 0)
return headerParams[i].value;
}
return NULL;
}
void HTTPServer::GetPerformanceData(SW_PerformanceData * p)
{
memcpy(p, &perfData, sizeof(perfData));
}
unsigned int HTTPServer::RecordPerformanceData(SW_PerformanceParam * param, unsigned int refTime)
{
unsigned int t_now = (unsigned int)PerformanceTimer.read_us();
param->TotalTime_us += (t_now - refTime);
param->Samples++;
if ((t_now - refTime) > param->MaxTime_us)
param->MaxTime_us = (t_now - refTime);
return t_now;
}
void HTTPServer::ResetPerformanceData()
{
memset(&perfData, 0, sizeof(perfData));
}