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.
Fork of SW_HTTPServer by
SW_HTTPServer.cpp
- Committer:
- WiredHome
- Date:
- 2013-08-11
- Revision:
- 12:109bf1558300
- Parent:
- 11:17d84c41a7b3
- Child:
- 13:8975d7928678
File content as of revision 12:109bf1558300:
//
// @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 _maxparams,
int _maxdynamicpages,
PC * _pc,
int _allocforheader,
int _allocforfile)
{
wifly = _wf;
webroot = (char *)malloc(strlen(_webroot)+1);
strcpy(webroot, _webroot);
maxparams = _maxparams;
maxdynamicpages = _maxdynamicpages;
params = (namevalue *)malloc(maxparams * sizeof(namevalue));
handlers = (handler *)malloc(maxdynamicpages * sizeof(handler));
headerbuffersize = _allocforheader;
headerbuffer = (char *)malloc(headerbuffersize);
pc = _pc;
queryType = NULL;
queryString = NULL;
hostString = NULL;
contentLength = NULL;
contentType = NULL;
authorization = NULL;
postQueryString = NULL;
paramcount = 0;
handlercount = 0;
maxheaderbytes = 0;
server = new TCPSocketServer();
server->bind(port);
server->listen();
// server->set_blocking(false, 0);
// client.set_blocking(false, 0);
// server->accept(client);
ResetPerformanceData();
PerformanceTimer.start();
}
HTTPServer::~HTTPServer()
{
int i;
for (i=0; i<handlercount; i++)
myfree(handlers[i].path);
myfree(headerbuffer);
myfree(handlers);
myfree(params);
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 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 = PerformanceTimer.read_us();
#ifdef DEBUG
pc->printf("Accept at %d\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<paramcount; i++) {
if (strcmp(params[i].name, name) == 0) {
return params[i].value;
}
}
return NULL;
}
// this=that&who=what&more=stuff...
// ^ ^ ^
void HTTPServer::ParseParameters(char * pName)
{
char * pVal;
char * pNextName;
// Parse params
pVal = strchr(pName, '#'); // If there is a '#fragment_id', we can ignore it
if (pVal)
*pVal = '\0';
do {
//pc->printf("Parse(%s)\r\n", pName);
//*pName++ = '\0'; // already '\0' on the first entry
params[paramcount].name = pName;
pVal = strchr(pName, '=');
pNextName = strchr(pName,'&');
if (pVal) {
if (pNextName == NULL || (pNextName && pNextName > pVal)) {
*pVal++ = '\0';
params[paramcount].value = pVal;
pName = pVal;
}
}
//pc->printf(" [%s]=[%s]\r\n", params[paramcount].name, params[paramcount].value);
paramcount++;
if (pNextName) {
pName = pNextName;
*pName++ = '\0';
} else {
pName = NULL;
}
} while (pName && paramcount < maxparams);
}
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(); //wifly->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, params, paramcount);
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 (hostString) {
myfree(hostString);
hostString = NULL;
}
if (contentLength) {
myfree(contentLength);
contentLength = NULL;
}
if (contentType) {
myfree(contentType);
contentType = NULL;
}
if (authorization) {
myfree(authorization);
authorization = 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 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 a record
bytecount = strlen(buffer);
if (bytecount > maxheaderbytes)
maxheaderbytes = bytecount;
while (eoRec) {
*eoRec = '\0';
if (*(eoRec-1) == '\r')
*(eoRec-1) = '\0';
#ifdef DEBUG
pc->printf("rec {%s}\r\n", soRec);
#endif
// 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");
}
//printf("GET: %s\r\n", queryString);
} else if (strstr(soRec, "POST ") == soRec) {
Extract(soRec, "POST", &queryString);
if (queryString) {
queryType = (char *)mymalloc(strlen("POST")+1);
strcpy(queryType, "POST");
}
//printf("POST: %s\r\n", queryString);
}
Extract(soRec, "Host: ", &hostString);
Extract(soRec, "Content-Length: ", &contentLength);
Extract(soRec, "Content-Type: ", &contentType);
Extract(soRec, "Authorization: ", &authorization);
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 params - if any
// /file.htm?name1=value1&name2=value2...
// /file.htm?name1&name2=value2...
paramcount = 0;
char * paramDelim = strchr(queryString, '?');
if (paramDelim) {
*paramDelim++ = '\0';
UnescapeString(paramDelim); // everything after the '?'
ParseParameters(paramDelim); // pointing at the NULL, but there are params beyond
}
//for (int i=0; i<paramcount; i++)
// pc->printf("param %d '%s'='%s'\r\n", i, params[i].name, params[i].value);
} 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(contentLength);
bool acceptIt = false;
//pc->printf("Content-Length = %d\r\n", postBytes);
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, params, paramcount);
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;
}
void HTTPServer::GetPerformanceData(SW_PerformanceData * p)
{
memcpy(p, &perfData, sizeof(perfData));
}
int HTTPServer::RecordPerformanceData(SW_PerformanceParam * param, int refTime)
{
int t_now = 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));
}
