Click here to Skip to main content
Click here to Skip to main content

Webio - An embedded web server

By , 20 Jul 2008
 

Introduction

Webio is a small-footprint web server, designed to be embedded in an application or an embedded system. It's useful when you want to implement a complex browser based GUI (which can be accessed by everything from PCs to cell phones) in a very efficient manner. Webio compiles and runs equally well under Linux and Windows, and should be easy to port to most other platforms. It comes with a programmer's manual (progman.html) which explains how to use it and how to port it.

Background

In 1996, I wrote one of the first web servers designed for embedded devices. In those days, most embedded devices had no file systems, so I created the "HTML compiler" to embed the files into the code image. Similarly, the lack of a file system led to C-language CGI functions.

Creating basic GUIs with this system was so easy that I started using it in Windows applications in preference to the Windows GUI. Back then, before JavaScript and CSS, it was somewhat limited - for example, it wouldn't make a very good "photo shop" type program - but for basic GUIs, it was great.

As my company started using Linux and Browser-enabled hand-held devices, a really huge advantage became obvious - my new applications worked everywhere, not just on Windows. The user was not tied on one OS or one type of device. They didn't even have to be near the machine running the application.

In 2007, I needed a similar server for an open-source project. I had left the previous company, which still retained the rights to the my old server. They wouldn't open source it, and nothing suitable was available in the public domain. I decided to create a second generation version of the server and release it under the BSD license so I would never have to write it again.

The result is Webio - my second (and hopefully last) embedded web server.

Using the code

The Windows version is probably of most interest to CodeProject readers, and so the .zip file is made available here. Follow these steps:

  1. Unzip it (preserving the directory structure).
  2. Type buildfs to compile the embedded file system.
  3. Open the project file with Visual C++ 6.0 or newer, and click Build.

You should get a little application which, when run, allows your PC to act as a web server - point a browser at it. You can do this in loopback by typing "http://127.1" in your browser's location bar.

Points of interest

Webio has a few improvements over my first embedded web server:

  • The "HTML compiler" is now a full-fledged file system builder, designed from the group up to generate not only file images in your C code, but also generate code for C-language CGI.
  • The server buffers all code-generated output, allowing accurate Content-Length fields on files with variable sized SSIs.
  • A fast path for binary files improves performance.
  • Portability across Windows/Linux/Embedded systems is enhanced.

History

  • July 2008 - First public release.
  • July 27th - Updated, call this release 1.1. Changes:
    • Added command line option to set the HTTP port (default is still 80).
    • Error message is more helpful if another web server already has port 80.
    • Fixed some typos and omissions in the manual.

License

This article, along with any associated source code and files, is licensed under The BSD License

About the Author

jbartas
Chief Technology Officer praemio.com
United States United States
Member
See my bio here:
 
http://www.bartas.net/resume.htm

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralRe: Heap failure after some timememberPanuO19 Apr '10 - 1:34 
Hi! Any news on this public SVN server? Or any other place to download newer than 1.1 release (www.praemio.com doesn't seem to exist any more) ?
Generalwi_putfilememberpbisiac8 Jul '09 - 21:20 
Hi, this is my (tested) upload routine:
 
/* wi_putfile()
*
* This is called when a session receives a PUT command.
*
*
* Returns: 0 if no error, else negative WIE_ error code.
*
*/
 
int
wi_putfile( wi_sess * sess)
{
   wi_file *fi;     /* info about current file */
 
   char *   cp;
   char *   cl;
   char *   rxend;
  
   int            res;
   long      b_read, b_remain;
  
   /* First find end of HTTP header */
   rxend = strstr(sess->ws_rxbuf, "\r\n\r\n" );  
  
   /* Extract the URL */
   cp = wi_nextarg(&sess->ws_rxbuf[3]);
   if(!cp)
   {
         wi_senderr(sess, 400);   /* Bad request */
         return WIE_CLIENT;
   }
   if(*cp == '/')
   {
         if(*(cp+1) == ' ')
            sess->ws_uri = wi_rootfile;
         else
            sess->ws_uri = cp+1;      /* strip leading slash */
   }
   else
         sess->ws_uri = cp;
 
   /* Extract other useful fields from header   */
   sess->ws_auth = wi_getline("Authorization:", cp);
   sess->ws_referer = wi_getline("Referer:", cp);
   sess->ws_host = wi_getline("Host:", cp);
 
   cl = wi_getline("Content-Length:", cp);
   if(cl)
         sess->ws_contentLength = atoi(cl);
   else
         sess->ws_contentLength = 0;   /* unset */       
       
   /* insert the null terminators in any strings in the rxbuf */
   if((sess->ws_uri > sess->ws_rxbuf) && (sess->ws_uri < rxend))
         wi_argterm(sess->ws_uri);         /* Null terminate the URI */
   if((sess->ws_auth > sess->ws_rxbuf) && (sess->ws_auth < rxend))
         wi_argterm(sess->ws_auth);      /* etc */
   if((sess->ws_referer > sess->ws_rxbuf) && (sess->ws_referer < rxend))
         wi_argterm(sess->ws_referer);
   if((sess->ws_uri > sess->ws_host) && (sess->ws_host < rxend))
         wi_argterm(sess->ws_host);
/*     ---------------------------------------------     */                
  
/*   Decode filename   */  
      wi_urldecode(sess->ws_uri);
     
/*   Inizio a leggere il file da scrivere nel mio filesystem     */
      b_remain = sess->ws_contentLength;
  
      res = wi_fopen(sess, fname_path, "w");
      if (res == 0)
      {    
      /*      file da scrivere */
        fi = sess->ws_filelist;
            
/*     vedo se nel primo pacchetto ci sono anche dati da scrivere     */
              if (sess->ws_data)
              {
                  b_read = sess->ws_rxsize - (sess->ws_data - sess->ws_rxbuf);
                  b_remain -= b_read;    
                  res = wi_fwrite(sess->ws_data, 1, b_read, fi);
              }
 
     if (b_remain > 0)
     do
            {
                  b_read = recv (sess->ws_socket, sess->ws_rxbuf,
                       sizeof(sess->ws_rxbuf), 0);
                   
                  if (b_read != -1)
           {
                 b_remain -= b_read;
                        res = wi_fwrite(sess->ws_rxbuf, 1, b_read, fi);
                        
           /*   all data written ? */
          if (res != b_read)
                        {
          /*   TODO: check, why not all data was written */
                b_remain = -1;     // segnalo abort
          }                   
           }
           else
           {
                 b_remain = -1;     // segnalo abort
          break;
           }
     }     //     do
     while (b_remain > 0);     
         
            if (b_remain == 0)
     {
                  wi_replyhdr(sess, sess->ws_contentLength);
                  wi_fclose(fi);
     }
     else
     {
           wi_fclose(fi);
           wi_fremove(sess->ws_uri);
                  wi_senderr(sess, 501);   /* Send "Internal server error" reply   */
     }
      }
      else
      {
     wi_senderr(sess, 503);   /* Send "Service Unavailable" reply   */
      }           
      return 0;         /* No Error */
}
 
Hope will be useful...
GeneralFile buffering and embedded systemmemberpbisiac7 Jul '09 - 6:03 
hi again,
 
if I ask Webio a "big" file (25Kbytes) it seems like wi_readfile allocates more and more sessions with
wi_txalloc(sess), until my embedded system runs out of memory.
Is that true ? could you help me modify this routine so no more than, say, 3 sessions get allocated at any time ?
I understand 26K is insignificant on Linux and Windows, but can be a problem in embedded system with limited heap...
GeneralRe: File buffering and embedded systemmemberjbartas7 Jul '09 - 18:32 
Hi -
 
Is this a binary file, or is a "normal" one which may have SSI-type includes?
 
This could be a bug (after all you've found 'em before Wink | ;-) ) or it could be an artifact of the way Webio handles the SSI-containing (I'll call them dynamic) files. To return the Content-Length of a dymanic file Webio reads the whole file, including files inside SSIs, into a chain of buffers; then measures the ready-to-send set of buffers to get the length. These buffers can eat a lot of space.
 
So - is the file binary?
 
Let me know,
-JB-
GeneralRe: File buffering and embedded systemmemberpbisiac7 Jul '09 - 21:06 
Hi, tanks for replying so fast,
 
it is a normal text file (license_lgpl.txt). If I change the extension to htm nothing changes. I guess It's treated as binary file.
GeneralRe: File buffering and embedded systemmemberpbisiac8 Jul '09 - 20:28 
Update: if I force sess->ws_flags |= WF_BINARY in wi_setftype download goes OK. The problem arise with non-binary files (wich get parsed and transferred to RAM before sending via Ethernet)
GeneralRe: File buffering and embedded systemmemberjbartas9 Jul '09 - 8:21 
Glad to hear you got it working. IIRC there's an option in the web page generator to force this flag on a per file basis, but when I went to check this out in the online manual I discovered the Praemio.com web site is defunct Sigh | :sigh: . Look like I have to find a new home for Webio and get everything moved. More soon..... -JB-
GeneralADDR_ABORT ARM exception on multiple GETmemberpbisiac3 Jul '09 - 5:22 
hi again (!)
 
If I refresh countinuosly my webpage my ARM system throws an ADDR_ABORT exception, probably due to recursion (but my stacks seem healty...) Any suggestion on how to avoid recursion on wi_readfile ?
GeneralRe: ADDR_ABORT ARM exception on multiple GETmemberpbisiac3 Jul '09 - 6:06 
Perhaps I've found the solution (memory leak). wi_alloc needs a check on malloc success:
 
wi_alloc(int bufsize)
{
char * buffer;
struct memmarker * mark;
int totalsize;
 
totalsize = bufsize + sizeof(struct memmarker) + 4;
 
buffer = WI_MALLOC(totalsize);

if (buffer == 0) // PB 03/07/2009 17.58.07
return (0); // PB 03/07/2009 17.58.12
...................
 
Hope this will help somebody
GeneralRe: ADDR_ABORT ARM exception on multiple GETmemberjbartas6 Jul '09 - 7:25 
This is definitely a bug. I'd better review the code for making sure I check all results of malloc. Thanks for pointing this out. -JB-

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web04 | 2.6.130523.1 | Last Updated 20 Jul 2008
Article Copyright 2008 by jbartas
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid