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:
Fri May 31 03:41:09 2013 +0000
Revision:
0:08da60dc31bc
Child:
2:5cd3f15ac98b
Initial revision of the Smart-WiFly-WebServer. Demo accesses credentials from the local file system. It can serve static pages from the file system (sd card, etc.), and it can serve dynamically generated pages, including handling GET and POST forms)

Who changed what in which revision?

UserRevisionLine numberNew contents of line
WiredHome 0:08da60dc31bc 1 /// Smart-WiFly-web server is a very primitive server using WiFly.
WiredHome 0:08da60dc31bc 2 ///
WiredHome 0:08da60dc31bc 3 /// Working version, but it is not using the standardized wifly
WiredHome 0:08da60dc31bc 4 /// library, which would not work for me... I had to make a number
WiredHome 0:08da60dc31bc 5 /// of changes to get it to work well.
WiredHome 0:08da60dc31bc 6 ///
WiredHome 0:08da60dc31bc 7 /// @caution If nothing visits its web page for a long time, it seems
WiredHome 0:08da60dc31bc 8 /// to disable the interface. Perhaps a sleep timer I'm not spotting?
WiredHome 0:08da60dc31bc 9 ///
WiredHome 0:08da60dc31bc 10 /// I created this because I couldn't fine one that worked and wanted to
WiredHome 0:08da60dc31bc 11 /// learn the WiFly module. There are a lot of possible improvements:
WiredHome 0:08da60dc31bc 12 /// @li I think I'm not using the Socket interface as fully as I should.
WiredHome 0:08da60dc31bc 13 /// @li I would like it to be non-blocking.
WiredHome 0:08da60dc31bc 14 /// @li I would like it to be faster (the interface from mbed to wifly is
WiredHome 0:08da60dc31bc 15 /// limited to 230400 baud before it drops chars.
WiredHome 0:08da60dc31bc 16 /// @li I would like to integrate this with the rtos.
WiredHome 0:08da60dc31bc 17 ///
WiredHome 0:08da60dc31bc 18 /// @note Copyright © 2013 by Smartware Computing, all rights reserved.
WiredHome 0:08da60dc31bc 19 /// Individuals may use this application for evaluation or non-commercial
WiredHome 0:08da60dc31bc 20 /// purposes. Within this restriction, changes may be made to this application
WiredHome 0:08da60dc31bc 21 /// as long as this copyright notice is retained. The user shall make
WiredHome 0:08da60dc31bc 22 /// clear that their work is a derived work, and not the original.
WiredHome 0:08da60dc31bc 23 /// Users of this application and sources accept this application "as is" and
WiredHome 0:08da60dc31bc 24 /// shall hold harmless Smartware Computing, for any undesired results while
WiredHome 0:08da60dc31bc 25 /// using this application - whether real or imagined.
WiredHome 0:08da60dc31bc 26 ///
WiredHome 0:08da60dc31bc 27 /// @author David Smart, Smartware Computing
WiredHome 0:08da60dc31bc 28 ///
WiredHome 0:08da60dc31bc 29 #include "mbed.h" // ver 63
WiredHome 0:08da60dc31bc 30 #include "SDFileSystem.h" // ver 2, standard
WiredHome 0:08da60dc31bc 31
WiredHome 0:08da60dc31bc 32 // Modified standard components
WiredHome 0:08da60dc31bc 33 #include "WiflyInterface.h" // ver 4+, derived from version 4 with my mods
WiredHome 0:08da60dc31bc 34
WiredHome 0:08da60dc31bc 35 // My components
WiredHome 0:08da60dc31bc 36 #include "SW_HTTPServer.h" // ver 0, the initial release.
WiredHome 0:08da60dc31bc 37 #include "Utility.h"
WiredHome 0:08da60dc31bc 38
WiredHome 0:08da60dc31bc 39 Serial pc(USBTX, USBRX);
WiredHome 0:08da60dc31bc 40
WiredHome 0:08da60dc31bc 41 LocalFileSystem local("local"); // some place to hold settings
WiredHome 0:08da60dc31bc 42 SDFileSystem sd(p5, p6, p7, p8, "sd"); // for the static web pages
WiredHome 0:08da60dc31bc 43
WiredHome 0:08da60dc31bc 44 BusOut leds(LED1,LED2,LED3,LED4); // dynamic page "/dyn2" lets you control these
WiredHome 0:08da60dc31bc 45
WiredHome 0:08da60dc31bc 46 // CopyFile
WiredHome 0:08da60dc31bc 47 //
WiredHome 0:08da60dc31bc 48 // An idea that didn't pan out since the local file does not support folders
WiredHome 0:08da60dc31bc 49 //
WiredHome 0:08da60dc31bc 50 bool CopyFile(const char * dst, const char * src) {
WiredHome 0:08da60dc31bc 51 FILE * fsrc;
WiredHome 0:08da60dc31bc 52 FILE * fdst;
WiredHome 0:08da60dc31bc 53 char fbuffer[1000];
WiredHome 0:08da60dc31bc 54 int bytes;
WiredHome 0:08da60dc31bc 55
WiredHome 0:08da60dc31bc 56 pc.printf("Copy to %s from %s\r\n", dst, src);
WiredHome 0:08da60dc31bc 57 fsrc = fopen(src,"rb");
WiredHome 0:08da60dc31bc 58 if (fsrc) {
WiredHome 0:08da60dc31bc 59 pc.printf(" opened %s\r\n", src);
WiredHome 0:08da60dc31bc 60 fdst = fopen(dst,"wb");
WiredHome 0:08da60dc31bc 61 if (fdst) {
WiredHome 0:08da60dc31bc 62 pc.printf(" opened %s\r\n", dst);
WiredHome 0:08da60dc31bc 63 bytes = fread(fbuffer,sizeof(char),sizeof(fbuffer),fsrc);
WiredHome 0:08da60dc31bc 64 while (bytes > 0) {
WiredHome 0:08da60dc31bc 65 fwrite(fbuffer, sizeof(char), bytes, fdst);
WiredHome 0:08da60dc31bc 66 bytes = fread(fbuffer,sizeof(char),sizeof(fbuffer),fsrc);
WiredHome 0:08da60dc31bc 67 }
WiredHome 0:08da60dc31bc 68 fclose(fdst);
WiredHome 0:08da60dc31bc 69 fclose(fsrc);
WiredHome 0:08da60dc31bc 70 pc.printf(" success.\r\n");
WiredHome 0:08da60dc31bc 71 return true;
WiredHome 0:08da60dc31bc 72 }
WiredHome 0:08da60dc31bc 73 fclose(fsrc);
WiredHome 0:08da60dc31bc 74 }
WiredHome 0:08da60dc31bc 75 pc.printf(" failed.\r\n");
WiredHome 0:08da60dc31bc 76 return false;
WiredHome 0:08da60dc31bc 77 }
WiredHome 0:08da60dc31bc 78
WiredHome 0:08da60dc31bc 79 void SimpleDynamicPage(HTTPServer *svr, const char * path, const HTTPServer::namevalue *params, int paramcount) {
WiredHome 0:08da60dc31bc 80 char buf[100];
WiredHome 0:08da60dc31bc 81
WiredHome 0:08da60dc31bc 82 // send the header
WiredHome 0:08da60dc31bc 83 svr->header(200, "OK", "Content-Type: text/html\r\n");
WiredHome 0:08da60dc31bc 84 // send some data
WiredHome 0:08da60dc31bc 85 svr->send("<html><head><title>Dynamic Page</title></head>\r\n");
WiredHome 0:08da60dc31bc 86 svr->send("<body>\r\n");
WiredHome 0:08da60dc31bc 87 svr->send("This page was generated dynamically. Create your own name=value pairs on the URL "
WiredHome 0:08da60dc31bc 88 "which uses the GET method.<br/>\r\n");
WiredHome 0:08da60dc31bc 89 sprintf(buf, "%d parameters passed to {%s}:<br/>\r\n", paramcount, path);
WiredHome 0:08da60dc31bc 90 svr->send(buf);
WiredHome 0:08da60dc31bc 91 // show each of the parameters passed on the URL
WiredHome 0:08da60dc31bc 92 for (int i=0; i<paramcount; i++) {
WiredHome 0:08da60dc31bc 93 sprintf(buf, "%d: %s = %s<br/>\r\n", i, params[i].name, params[i].value);
WiredHome 0:08da60dc31bc 94 svr->send(buf);
WiredHome 0:08da60dc31bc 95 }
WiredHome 0:08da60dc31bc 96 svr->send("the end.</body></html>\r\n");
WiredHome 0:08da60dc31bc 97 }
WiredHome 0:08da60dc31bc 98
WiredHome 0:08da60dc31bc 99 void SimpleDynamicForm(HTTPServer *svr, const char * path, const HTTPServer::namevalue *params, int paramcount) {
WiredHome 0:08da60dc31bc 100 char buf[100];
WiredHome 0:08da60dc31bc 101
WiredHome 0:08da60dc31bc 102 // set the LEDs based on a passed in parameter.
WiredHome 0:08da60dc31bc 103 leds = atoi(svr->GetParameter("leds"));
WiredHome 0:08da60dc31bc 104 // send the header
WiredHome 0:08da60dc31bc 105 svr->header(200, "OK", svr->GetSupportedType(".htm")); //svr->header(200, "OK", "Content-Type: text/html\r\n");
WiredHome 0:08da60dc31bc 106 // send some data
WiredHome 0:08da60dc31bc 107 svr->send("<html><head><title>Dynamic Page</title></head>\r\n");
WiredHome 0:08da60dc31bc 108 svr->send("<body>\r\n");
WiredHome 0:08da60dc31bc 109 svr->send("This form was generated dynamically. You can add name=value pairs on the URL "
WiredHome 0:08da60dc31bc 110 "which will show up in the form, but the form is submitted using POST method.<br/>\r\n");
WiredHome 0:08da60dc31bc 111 svr->send("Hint: leds=7 turns on 3 of the 4 blue leds on the mbed<br/>\r\n");
WiredHome 0:08da60dc31bc 112 sprintf(buf, "%d parameters passed to {%s}:<br/>\r\n", paramcount, path);
WiredHome 0:08da60dc31bc 113 svr->send(buf);
WiredHome 0:08da60dc31bc 114 // Create a user form for which they can post changes
WiredHome 0:08da60dc31bc 115 sprintf(buf, "<form method='post' action='%s'>\r\n", path);
WiredHome 0:08da60dc31bc 116 svr->send(buf);
WiredHome 0:08da60dc31bc 117 // show the parameters in a nice format
WiredHome 0:08da60dc31bc 118 svr->send("<table>\r\n");
WiredHome 0:08da60dc31bc 119 for (int i=0; i<paramcount; i++) {
WiredHome 0:08da60dc31bc 120 sprintf(buf, "<tr><td>%d</td><td>%s</td><td><input type='text' name='%s' value='%s'></td></tr>\r\n", i, params[i].name, params[i].name, params[i].value);
WiredHome 0:08da60dc31bc 121 svr->send(buf);
WiredHome 0:08da60dc31bc 122 }
WiredHome 0:08da60dc31bc 123 svr->send("<tr><td>&nbsp;</td><td colspan='2'><input type='submit' value='submit'><input type='reset' value='clear'></td></tr>\r\n");
WiredHome 0:08da60dc31bc 124 svr->send("</table>\r\n");
WiredHome 0:08da60dc31bc 125 svr->send("</form>\r\n");
WiredHome 0:08da60dc31bc 126 // see how we're doing with free memory
WiredHome 0:08da60dc31bc 127 sprintf(buf,"Free memory space: %d\r\n", Free());
WiredHome 0:08da60dc31bc 128 svr->send(buf);
WiredHome 0:08da60dc31bc 129 svr->send("the end.</body></html>\r\n");
WiredHome 0:08da60dc31bc 130 }
WiredHome 0:08da60dc31bc 131
WiredHome 0:08da60dc31bc 132 typedef struct
WiredHome 0:08da60dc31bc 133 {
WiredHome 0:08da60dc31bc 134 char * ssid;
WiredHome 0:08da60dc31bc 135 char * pass;
WiredHome 0:08da60dc31bc 136 } credentials;
WiredHome 0:08da60dc31bc 137
WiredHome 0:08da60dc31bc 138 #define CREDENTIAL_SIZE 50
WiredHome 0:08da60dc31bc 139 bool ReadCredentials(char * file, credentials * cr)
WiredHome 0:08da60dc31bc 140 {
WiredHome 0:08da60dc31bc 141 FILE *fp = fopen(file, "r");
WiredHome 0:08da60dc31bc 142
WiredHome 0:08da60dc31bc 143 cr->ssid = (char *)malloc(CREDENTIAL_SIZE);
WiredHome 0:08da60dc31bc 144 cr->pass = (char *)malloc(CREDENTIAL_SIZE);
WiredHome 0:08da60dc31bc 145 if (fp) {
WiredHome 0:08da60dc31bc 146 fgets(cr->ssid, CREDENTIAL_SIZE, fp);
WiredHome 0:08da60dc31bc 147 fgets(cr->pass, CREDENTIAL_SIZE, fp);
WiredHome 0:08da60dc31bc 148 fclose(fp);
WiredHome 0:08da60dc31bc 149 RTrim(cr->ssid);
WiredHome 0:08da60dc31bc 150 RTrim(cr->pass);
WiredHome 0:08da60dc31bc 151 return true;
WiredHome 0:08da60dc31bc 152 } else {
WiredHome 0:08da60dc31bc 153 return false;
WiredHome 0:08da60dc31bc 154 }
WiredHome 0:08da60dc31bc 155 }
WiredHome 0:08da60dc31bc 156
WiredHome 0:08da60dc31bc 157 void FreeCredentials(credentials * cr)
WiredHome 0:08da60dc31bc 158 {
WiredHome 0:08da60dc31bc 159 // first secure them by wiping them out
WiredHome 0:08da60dc31bc 160 for (int i=0; i<CREDENTIAL_SIZE; i++) {
WiredHome 0:08da60dc31bc 161 cr->ssid[i] = cr->pass[i] = '*';
WiredHome 0:08da60dc31bc 162 }
WiredHome 0:08da60dc31bc 163 // then free the memory
WiredHome 0:08da60dc31bc 164 free(cr->ssid);
WiredHome 0:08da60dc31bc 165 free(cr->pass);
WiredHome 0:08da60dc31bc 166 }
WiredHome 0:08da60dc31bc 167
WiredHome 0:08da60dc31bc 168 int main() {
WiredHome 0:08da60dc31bc 169 credentials cr;
WiredHome 0:08da60dc31bc 170
WiredHome 0:08da60dc31bc 171 pc.baud(460800);
WiredHome 0:08da60dc31bc 172 pc.printf("\r\nSmart WiFly 4 - build " __DATE__ " " __TIME__ "\r\n");
WiredHome 0:08da60dc31bc 173
WiredHome 0:08da60dc31bc 174 if (!ReadCredentials("/local/user.txt", &cr)) {
WiredHome 0:08da60dc31bc 175 pc.printf("**** ERROR, no /local/user.txt file was found. ****\r\n");
WiredHome 0:08da60dc31bc 176 pc.printf(" awaiting watchdog.\r\n");
WiredHome 0:08da60dc31bc 177 while(1);
WiredHome 0:08da60dc31bc 178 }
WiredHome 0:08da60dc31bc 179 WiflyInterface wifly(p28, p27, p23, p24, cr.ssid, cr.pass, WPA);
WiredHome 0:08da60dc31bc 180 FreeCredentials(&cr);
WiredHome 0:08da60dc31bc 181
WiredHome 0:08da60dc31bc 182 wifly.init(); // use DHCP
WiredHome 0:08da60dc31bc 183 wifly.baud(230400); // drops chars at 460800
WiredHome 0:08da60dc31bc 184
WiredHome 0:08da60dc31bc 185 while (!wifly.connect()) {
WiredHome 0:08da60dc31bc 186 printf(" ... failed to connect, retrying.\r\n");
WiredHome 0:08da60dc31bc 187 wifly.reset();
WiredHome 0:08da60dc31bc 188 wait(1.0);
WiredHome 0:08da60dc31bc 189 }
WiredHome 0:08da60dc31bc 190 printf("IP Address is %s\n\r", wifly.getIPAddress());
WiredHome 0:08da60dc31bc 191
WiredHome 0:08da60dc31bc 192 // Copy /sd/web to /local/web [local file system does not support subdirs]
WiredHome 0:08da60dc31bc 193 if (0) {
WiredHome 0:08da60dc31bc 194 printf("Open directory on /sd/web\r\n");
WiredHome 0:08da60dc31bc 195 DIR *d = opendir("/sd/web");
WiredHome 0:08da60dc31bc 196 struct dirent *p;
WiredHome 0:08da60dc31bc 197 while((p = readdir(d)) != NULL) {
WiredHome 0:08da60dc31bc 198 printf("%s\r\n", p->d_name);
WiredHome 0:08da60dc31bc 199 char sfqfn[40], dfqfn[40];
WiredHome 0:08da60dc31bc 200 sprintf(sfqfn,"%s/%s","/sd/web", p->d_name);
WiredHome 0:08da60dc31bc 201 sprintf(dfqfn,"%s/%s","/local", p->d_name);
WiredHome 0:08da60dc31bc 202 CopyFile(dfqfn,sfqfn);
WiredHome 0:08da60dc31bc 203 }
WiredHome 0:08da60dc31bc 204 closedir(d);
WiredHome 0:08da60dc31bc 205 printf("Directory closed\r\n");
WiredHome 0:08da60dc31bc 206 }
WiredHome 0:08da60dc31bc 207
WiredHome 0:08da60dc31bc 208 #define HTTP_SERVER_PORT 80
WiredHome 0:08da60dc31bc 209 HTTPServer svr(&wifly, HTTP_SERVER_PORT, "/sd/web/", 10, 10, &pc);
WiredHome 0:08da60dc31bc 210 svr.RegisterHandler("/dyn1", SimpleDynamicPage);
WiredHome 0:08da60dc31bc 211 svr.RegisterHandler("/dyn2", SimpleDynamicForm);
WiredHome 0:08da60dc31bc 212
WiredHome 0:08da60dc31bc 213 pc.printf("Waiting for a connection...\r\n");
WiredHome 0:08da60dc31bc 214 while (true)
WiredHome 0:08da60dc31bc 215 {
WiredHome 0:08da60dc31bc 216 svr.ip_process();
WiredHome 0:08da60dc31bc 217 }
WiredHome 0:08da60dc31bc 218 }
WiredHome 0:08da60dc31bc 219