A sample program demonstrating a small but powerful web server using the Wifly module. This uses several libraries from others, but has a custom version of the WiflyInterface library, with numerous improvement to the mbed standard library.

Dependencies:   SW_HTTPServer WiflyInterface mbed C12832 IniManager

Here's the code

But you also might want to check out the SmartBoard-WiFly project page.

Basic Web Server

  • Serves static files from the selected file system. This is a compile-time setting, and a typical configuration supports gif, jpg, jpeg, ico, png, zip, gz, tar, txt, pdf, htm, and html.
  • It is designed to be small, thereby better supporting the limited resources of an embedded environment.

Advanced Web Services

  • Serves dynamically generated pages, where your software registers for a path, and then everything to that path activates your handler. Your handler then defines the header and body response.
  • Dynamic handlers can process GET query parameters (e.g. /dyn1?sky=blue&grass=green).
  • Dynamic handlers can process POST query parameters, as delivered from submission of a form.
  • Dynamic handlers can protect a resource with user:password access.

Run-Time Configurations

  • File System Support - using either the "local" file system supported by the magic chip, or from either an SD-Card or a USB flash drive.
  • Configurable to the maximum number of dynamic handlers (minimize memory requirements).
  • Configurable to the maximum number of name=value pairs for dynamic handlers (minimize memory requirements).

Compile-Time Configurations

  • Default filename for URL ending in '/' - default is 'index.htm'.
  • Configurable buffer sizes permit minimizing RAM requirements.
  • Configurable header response information.
  • Configurable for which serial port is used to communicate to the WiFly module.
  • Improved security option - to disable telnet access.

Diagnostics

  • API to determine the largest header (to more efficiently size the buffers).
  • API to gather the run-time characteristics - header processing time and content delivery time.

Limitations / Constraints

Known Issues

These are known issues, not yet resolved.

  1. Occasionally fails to serve a page - one test will constantly reload a web page every 30 seconds. It may run for hours, or minutes, then fail to load. Behaviors then are:
    • Hit the reload button in the browser and away it goes.
    • Hit the reload and you'll see the Wifly LEDs energize from the request, but no response by the web server. It appears that the embedded code does not "accept()" the connection in the TCP Socket Server.
      • In this case, the Wifly module has gone through an internal watchdog reset and the configuration parameters are such that it does not gracefully recover. Microchip is aware of this issue, but has not solved it.

Wifly Limitations

  • Single thread - it doesn't respond to overlapping requests (e.g. an embedded image may be requested before the main page completes transfer - the request is lost and the image not shown).
  • Single client - goes along with the single thread, but it doesn't support more than one client at a time.

Smart-Wifly-WebServer

  • Dynamic memory allocation - it does use dynamic memory allocation, which would be discouraged/avoided in many embedded systems. Here it uses it in parsing a request and it releases those resources upon completion of that request. If there is no other dynamic allocation that persists beyond a transaction, it should not cause memory fragmentation. Note that with multi-threading (if this is implemented with an OS), you then have race conditions that could cause fragmentation.

Web Server

Here's the web server in action. A combination of static pages served from the file system and dynamically generated pages.

/media/uploads/WiredHome/swsvr_1.pngPart of the main demo page,
which basically has all the
specifications, configurations, and limitations.
/media/uploads/WiredHome/swsvr_2.pngA zoomed out view of the same page.
/media/uploads/WiredHome/swsvr_3.pngIt would be possible to configure
the server via the web.
/media/uploads/WiredHome/swsvr_4.pngOne of the dynamically generated pages.
This one has parsed the query parameters.
/media/uploads/WiredHome/swsvr_5.pngA simple form which has a dynamic handler on the back end.
Here it takes the value associated with "leds"
and uses that to set the 4 LEDs on the mbed module.
/media/uploads/WiredHome/swsvr_6.pngA dynamic handler can require authentication.
/media/uploads/WiredHome/swsvr_7.pngSuccess!

But I've now gone so far beyond that in the current version. Here's what this one can do:

  1. It serves static web pages from a file system. I've only tested with the local file system and an SD card, but should work for any, so long as you remember that the local file system can't read subdirectories.
  2. It can serve dynamically generated web pages. This lets you accept name=value pairs using the URL (using either a GET or POST method). It can also accept them from forms. The demo lets you control the 4 LEDs from a form.
  3. As safely as possible it retrieves your credentials to the Wi-Fi Access Point. After using them, it overwrites that ram so they can't be as easily extracted.
  4. I made a large number of changes to the Wifly driver. It had too short of a timeout and I found quite a number of optimizations for performance and robustness.
  5. I have the start on a security feature - you can configure a resource to require user credentials to access it. The browser typically provides a username and password dialog. Take care however, as it does not support a secure (https) connection, so the credentials are not as securely transferred as I would like.

Optimizations I'd like to do:

  1. speed it up - I'm running the mbed to wifly module interface at 230K, which is about the top speed w/o flow control. There are other places where some time delays remain - I have eliminated a number of them.
  2. make it non-blocking, so other work can happen.
  3. integrate it with the rtos
  4. When a web page has referenced resources (e.g. an image tag), it rarely loads the image on the first try. I think the request for the resource comes in while it is still in the WiflyInterface cleaning up from the last connection. The Wifly module supports only a single connection at a time. I worked around this with a small bit of javascript to load the images after the web page.

But all in all I think it is a good start.

Program prerequisite

Here's the link to the program, but when you open it up, note a few very important items.

  1. Port Numbers listed in the constructor match the SmartBoard Baseboard.
  2. I sped up the communication baud rate to the mbed from the default 9600. Match your terminal program accordingly.
  3. Download this zip. Place it and an unzipped copy into the mbed local file system. These are the demo files.
  4. The typical ssid and password are not shown. See below to set yours.

ssid and password

You need to create a simple text file on your mbed root folder named "config.ini". The easiest way perhaps is to create "config.txt", add the information shown below and then rename it. This will be read at startup to connect you to the network. Something quite simple, like this:

[Wifi]
ssid=your_ssid
pass=your_pass_code

The program

And the program.

Import programSmart-WiFly-WebServer

A sample program demonstrating a small but powerful web server using the Wifly module. This uses several libraries from others, but has a custom version of the WiflyInterface library, with numerous improvement to the mbed standard library.

Committer:
WiredHome
Date:
Sat Oct 12 00:20:18 2013 +0000
Revision:
30:4717cc1f970e
Parent:
29:14c47d31a9dc
Child:
33:41ac99847df8
Picked up a replacement MODSERIAL library

Who changed what in which revision?

UserRevisionLine numberNew contents of line
WiredHome 26:eea4db568404 1 /// Demonstration of dynamic page creation using the Smartware Web Server
WiredHome 26:eea4db568404 2 ///
WiredHome 26:eea4db568404 3 /// Here is a sample for file upload.
WiredHome 26:eea4db568404 4
WiredHome 26:eea4db568404 5 #include "SW_HTTPServer.h"
WiredHome 26:eea4db568404 6 #include "DynamicFileIn.h"
WiredHome 26:eea4db568404 7 #include "Utility.h"
WiredHome 26:eea4db568404 8
WiredHome 26:eea4db568404 9 typedef enum {
WiredHome 26:eea4db568404 10 IDLE,
WiredHome 26:eea4db568404 11 ACCEPTED,
WiredHome 26:eea4db568404 12 REJECTED_TOO_LARGE
WiredHome 26:eea4db568404 13 } AcceptStatus;
WiredHome 26:eea4db568404 14
WiredHome 26:eea4db568404 15 /// DynamicFileIn
WiredHome 26:eea4db568404 16 ///
WiredHome 26:eea4db568404 17 /// This page lets you submit a file.
WiredHome 26:eea4db568404 18 ///
WiredHome 26:eea4db568404 19 /// You can see in main how this page was registered.
WiredHome 26:eea4db568404 20 ///
WiredHome 29:14c47d31a9dc 21 HTTPServer::CallBackResults DynamicFileIn(HTTPServer *svr, HTTPServer::CallBackType type, const char * path, const HTTPServer::namevalue *params, int paramcount)
WiredHome 26:eea4db568404 22 {
WiredHome 26:eea4db568404 23 char buf[100];
WiredHome 26:eea4db568404 24 int clSize; // content-length size to be transferred
WiredHome 26:eea4db568404 25 char * p1; // general purpose pointer for processing the results
WiredHome 26:eea4db568404 26 char * p2; // general purpose pointer for processing the results
WiredHome 26:eea4db568404 27 static char * boundary = ""; // for pointing at the boundary marker part of multipart/form-data
WiredHome 26:eea4db568404 28 static AcceptStatus accept = IDLE;
WiredHome 29:14c47d31a9dc 29 HTTPServer::CallBackResults ret = HTTPServer::ACCEPT_ERROR;
WiredHome 26:eea4db568404 30 DIR *d;
WiredHome 26:eea4db568404 31 struct dirent *p;
WiredHome 26:eea4db568404 32
WiredHome 26:eea4db568404 33 switch (type) {
WiredHome 30:4717cc1f970e 34
WiredHome 26:eea4db568404 35 case HTTPServer::CONTENT_LENGTH_REQUEST:
WiredHome 26:eea4db568404 36 // Here we can find out how much data is headed our way,
WiredHome 26:eea4db568404 37 // and choose to accept or reject it.
WiredHome 26:eea4db568404 38 clSize = atoi(svr->GetHeaderValue("Content-Length"));
WiredHome 26:eea4db568404 39 //printf("Content Len RQST(%d)\r\n", clSize);
WiredHome 26:eea4db568404 40 //printf("Content-Type: [%s]\r\n", svr->GetHeaderValue("Content-Type"));
WiredHome 26:eea4db568404 41 // also, for the multipart data, we have to learn and track
WiredHome 26:eea4db568404 42 // the boundary sequence, so we can parse the big block
WiredHome 26:eea4db568404 43 // successfully.
WiredHome 26:eea4db568404 44 boundary = strstr((char *)svr->GetHeaderValue("Content-Type"), "boundary=");
WiredHome 26:eea4db568404 45 if (boundary)
WiredHome 26:eea4db568404 46 boundary += strlen("boundary=");
WiredHome 26:eea4db568404 47 //printf("InFile = [%s]\r\n", svr->GetHeaderValue("InFile"));
WiredHome 26:eea4db568404 48 // If we're happy with the size, and we have the boundary marker we need
WiredHome 26:eea4db568404 49 if (clSize < 3000 && boundary) { // arbitrarily chosen size to accept
WiredHome 26:eea4db568404 50 accept = ACCEPTED;
WiredHome 29:14c47d31a9dc 51 ret = HTTPServer::ACCEPT_COMPLETE;
WiredHome 26:eea4db568404 52 } else {
WiredHome 26:eea4db568404 53 accept = REJECTED_TOO_LARGE;
WiredHome 26:eea4db568404 54 }
WiredHome 26:eea4db568404 55 break;
WiredHome 30:4717cc1f970e 56
WiredHome 26:eea4db568404 57 case HTTPServer::DATA_TRANSFER:
WiredHome 26:eea4db568404 58 // Here's the big blob of data. We've got some painful work here.
WiredHome 26:eea4db568404 59 // The blob looks kind of like this:
WiredHome 26:eea4db568404 60 //---
WiredHome 26:eea4db568404 61 //Content-Disposition: form-data; name="InFile"; filename="somefile.txt"\r\n
WiredHome 26:eea4db568404 62 //Content-Type: text/plain\r\n
WiredHome 26:eea4db568404 63 //\r\n
WiredHome 26:eea4db568404 64 //Text in the file starts here\r\n
WiredHome 26:eea4db568404 65 //and continues as needed.
WiredHome 26:eea4db568404 66 //------webKitFormBoundary9A3C872FAC9BE23--\r\n
WiredHome 26:eea4db568404 67 //---
WiredHome 26:eea4db568404 68 // So, we'll work our way thru by extracting the filename, we'll
WiredHome 26:eea4db568404 69 // assume it is text/plain, as in this example.
WiredHome 26:eea4db568404 70 // If all looks well, we'll write it to the local file system,
WiredHome 26:eea4db568404 71 // and return true.
WiredHome 26:eea4db568404 72 printf("Data Transfer\r\n");
WiredHome 26:eea4db568404 73 p1 = strchr((char *)path, '\r');
WiredHome 26:eea4db568404 74 if (p1) {
WiredHome 26:eea4db568404 75 p2 = strstr((char *)path, "filename=\"");
WiredHome 26:eea4db568404 76 if (p2) {
WiredHome 26:eea4db568404 77 p2 += strlen("filename=\"");
WiredHome 26:eea4db568404 78 p1 = strchr(p2, '"');
WiredHome 26:eea4db568404 79 if (p1) {
WiredHome 26:eea4db568404 80 char * fn = p2;
WiredHome 26:eea4db568404 81 *p1 = '\0';
WiredHome 26:eea4db568404 82 p1 = strstr(p1+1, "\r\n\r\n");
WiredHome 26:eea4db568404 83 if (p1) {
WiredHome 26:eea4db568404 84 p1 += 4; // finally, sitting on the start of the file data
WiredHome 26:eea4db568404 85 p2 = strstr(p1, boundary); // find the end
WiredHome 26:eea4db568404 86 if (p2) {
WiredHome 26:eea4db568404 87 // now, create the file
WiredHome 26:eea4db568404 88 FILE * fp;
WiredHome 26:eea4db568404 89 *p2 = '\0';
WiredHome 26:eea4db568404 90 strcpy(buf, svr->GetWebRoot());
WiredHome 26:eea4db568404 91 strcat(buf, "/");
WiredHome 26:eea4db568404 92 strcat(buf, fn);
WiredHome 26:eea4db568404 93 printf("Writing to [%s]\r\n", buf);
WiredHome 26:eea4db568404 94 fp = fopen(buf, "w");
WiredHome 26:eea4db568404 95 if (fp) {
WiredHome 26:eea4db568404 96 printf("writing data...\r\n");
WiredHome 26:eea4db568404 97 fwrite(p1, 1, p2 - p1, fp);
WiredHome 26:eea4db568404 98 fclose(fp);
WiredHome 26:eea4db568404 99 printf("done.\r\n");
WiredHome 29:14c47d31a9dc 100 ret = HTTPServer::ACCEPT_COMPLETE;
WiredHome 26:eea4db568404 101 } else {
WiredHome 26:eea4db568404 102 printf("failed to open [%s]\r\n", buf);
WiredHome 26:eea4db568404 103 }
WiredHome 26:eea4db568404 104 }
WiredHome 26:eea4db568404 105 }
WiredHome 26:eea4db568404 106 }
WiredHome 26:eea4db568404 107 }
WiredHome 26:eea4db568404 108 }
WiredHome 29:14c47d31a9dc 109 ret = HTTPServer::ACCEPT_COMPLETE;
WiredHome 26:eea4db568404 110 break;
WiredHome 30:4717cc1f970e 111
WiredHome 30:4717cc1f970e 112 case HTTPServer::SEND_PAGE:
WiredHome 30:4717cc1f970e 113 // send the header
WiredHome 30:4717cc1f970e 114 //printf("Sending page\r\n");
WiredHome 30:4717cc1f970e 115 svr->header(200, "OK", svr->GetSupportedType(".htm"));
WiredHome 30:4717cc1f970e 116 // send some data
WiredHome 30:4717cc1f970e 117 svr->send("<html><head><title>Dynamic File Submit</title></head>\r\n");
WiredHome 30:4717cc1f970e 118 svr->send("<body>\r\n");
WiredHome 30:4717cc1f970e 119 svr->send("<h1>Smart WiFly Web Server</h1>\r\n");
WiredHome 30:4717cc1f970e 120 svr->send("This example permits you to upload a small file. Select the file using ");
WiredHome 30:4717cc1f970e 121 svr->send("the normal form gadget and submit it.");
WiredHome 30:4717cc1f970e 122 switch(accept) {
WiredHome 30:4717cc1f970e 123 case IDLE:
WiredHome 30:4717cc1f970e 124 default:
WiredHome 30:4717cc1f970e 125 break;
WiredHome 30:4717cc1f970e 126 case ACCEPTED:
WiredHome 30:4717cc1f970e 127 svr->send("<br/><b>Previous submission accepted.</b><br/>\r\n");
WiredHome 30:4717cc1f970e 128 break;
WiredHome 30:4717cc1f970e 129 case REJECTED_TOO_LARGE:
WiredHome 30:4717cc1f970e 130 svr->send("<br/><b>Previous submission rejected.</b><br/>\r\n");
WiredHome 30:4717cc1f970e 131 break;
WiredHome 30:4717cc1f970e 132 }
WiredHome 30:4717cc1f970e 133 // note that to accept a file, the form enctype is multipart/form-data.
WiredHome 30:4717cc1f970e 134 sprintf(buf, "<form method='post' enctype='multipart/form-data' action='%s'>\r\n", path);
WiredHome 30:4717cc1f970e 135 svr->send(buf);
WiredHome 30:4717cc1f970e 136 svr->send("<table border='1'>\r\n");
WiredHome 30:4717cc1f970e 137 svr->send("<tr><td>&nbsp;</td><td>File</td><td><input type='file' name='InFile' size='40'></td></tr>\r\n");
WiredHome 30:4717cc1f970e 138 svr->send("<tr><td>&nbsp;</td><td colspan='2'><input type='submit' value='submit'><input type='reset' value='clear'></td></tr>\r\n");
WiredHome 30:4717cc1f970e 139 svr->send("</table>\r\n");
WiredHome 30:4717cc1f970e 140 svr->send("</form>\r\n");
WiredHome 30:4717cc1f970e 141 #if 1
WiredHome 30:4717cc1f970e 142 sprintf(buf, "Directory of [%s]:<br/>\r\n", svr->GetWebRoot());
WiredHome 30:4717cc1f970e 143 svr->send(buf);
WiredHome 30:4717cc1f970e 144 d = opendir(svr->GetWebRoot());
WiredHome 30:4717cc1f970e 145 if ( d != NULL ) {
WiredHome 30:4717cc1f970e 146 while ( (p = readdir(d)) != NULL ) {
WiredHome 30:4717cc1f970e 147 sprintf(buf, " - %s<br/>\r\n", p->d_name);
WiredHome 30:4717cc1f970e 148 svr->send(buf);
WiredHome 30:4717cc1f970e 149 }
WiredHome 30:4717cc1f970e 150 closedir(d);
WiredHome 30:4717cc1f970e 151 } else {
WiredHome 30:4717cc1f970e 152 svr->send("Unable to open directory!");
WiredHome 30:4717cc1f970e 153 }
WiredHome 30:4717cc1f970e 154 #endif
WiredHome 30:4717cc1f970e 155 svr->send("<br/><a href='/'>Back to main</a></body></html>\r\n");
WiredHome 30:4717cc1f970e 156 ret = HTTPServer::ACCEPT_COMPLETE;
WiredHome 30:4717cc1f970e 157 break;
WiredHome 30:4717cc1f970e 158
WiredHome 26:eea4db568404 159 default:
WiredHome 26:eea4db568404 160 printf("unknown command %d\r\n", type);
WiredHome 29:14c47d31a9dc 161 ret = HTTPServer::ACCEPT_ERROR;
WiredHome 26:eea4db568404 162 break;
WiredHome 26:eea4db568404 163 }
WiredHome 26:eea4db568404 164 return ret;
WiredHome 26:eea4db568404 165 }
WiredHome 26:eea4db568404 166
WiredHome 29:14c47d31a9dc 167