This wikipage is dedicated to GoAhead Open Source Embedded Webserver.
For more information visit the main page.
Start webserver in daemon mode (UNIX/Linux)
On Unix systems (like Linux) if the webserver is started from the command line it will stop working after closing the terminal.
There are 2 methods to avoid this to happen:
- Using the shell command nohup, this does not requires code changes and recompilation:
$nohup webs &
- Changing the webserver code (the main.c file) to perform a fork() and then in the child process setsid() to become a session leader:
pid_t pid; pid = fork(); switch ( pid ) { case -1: /* fork() failed -- exit with an error status */ perror("forking daemon"); break; case 0: /* we're the child process -- carry on... */ break; default: /* we're the parent process -- exit with success status */ exit(0); break; } if ( setsid() < 0 ) { perror("setsid()"); }
Add support for sessions
What is a session?
A method to track user state during web browsing.
Cookies
You have to add a Set-Cookie: HTTP pragma when replying to a form submission or use http meta-equiv tags in the generated page.
For implementation you have the following options:
- modify the server so that outgoing headers are buffered instead of being sent immediately. Then you could create code that creates the outgoing cookie strings and adds them to the headers.
Downside – you also need to be able to buffer your HTML/ASP output, and blast everything out when the page is actually complete. - write a special GoForms form handler that knows how to generate the cookie strings. In your ejScript code that wants to generate cookies, build a string that is an URL-encoded version of the keys/parameters used in the cookies and then redirect the user's browser to the URL for that form handler, passing the cookie data in as a query string. Your cookie form handler converts the URL-encoded key/value pairs to cookie-encoded key/value pairs and creates all its own headers (including the new cookies) and sends them to the user's browser. Then the server redirects the browser back to the page that you really wanted to serve at the beginning of this example.
- create an ejScript function that generates as its output client-side javascript that sets the cookie value. This requires that your users have javascript enabled (obviously). There may also be cross-browser issues with this approach (I don't know …).
Implementation here: ASP cookies
See the following links for more information:
- General documentation on using cookies.
- Simple example to set and read cookie value.
Alternative method
Session support solution from csyap using user/pass for storage.
Other methods
Using the information from the wp structure a hash identifier for the curent user can be generated (based on IP and other browser supplied information).
Prevent content caching
By default most http content (images, static html files) are cached. This means that after the first visit the files are retrieved from the local cache and not from the web server.
In some cases this is not desirable so we'll describe here methods to disable content caching.
HTTP header
The following HTTP protocol pragma's are automatically add to ASP pages (dynamic pages):
Pragma: no-cache Cache-control: no-cache
This should work with most browser clients although according to HTTP/1.0 these headers are intended for HTTP proxies.
HTML header
Another method that can be used to prevent caching is to use the following HTML directives in the web page header.
<meta http-equiv="Cache-Control" content="no-cache"> <meta http-equiv="Pragma" content="no-cache">
which help with the users browser cache but not with most proxy servers.
Internet Explorer
Special notes regarding IE:
- for problems with content caching see this MS knowledge base article.
- the browser's Back (and Forward) button is a “special” case for which you'll always get the cached page:
“However, the page remains in the disk cache (Temporary Internet Files) and is used in appropriate situations without contacting the remote Web server, such as when the BACK and FORWARD buttons are used to access the navigation history or when the browser is in offline mode.” - use HTTP/1.1 protocol in the response.
How to debug communication problems at the protocol level
If you suspect problems at the HTTP communication level you can try out the following to debug the problem:
- Telnet to the webserver port and check for the response from the server:
# Server running on port 80 $telnet www.prolix.ro 80 GET / HTTP/1.0<ENTER> <ENTER>
The ENTER key must be pressed twice, required by the HTTP protocol specification. This gives you access to the HTTP protocol headers the same way as method #2.
- Firefox Browser + Live HTTP Headers Extension can be used to examine the HTTP headers exchanged between browser and webserver.
For Internet Explorer there is ieHTTPHeaders. - Specialized sniffers for HTTP protocol like HTTP Sniffer (Windows version only).
File upload
There are 2 methods to do this.
Patch
The file upload patch is listed on the GoAhead website.
Latest version 1.1.4 requires some modifications to work with GoAhead Webserver 2.1.8.
It compiles without errors with version 2.1.7.
To understand how this works take a look at the patch. There is an upload goform and the uploaded file will be stored in the default path (check the source code).
CGI
Using a CGI script that will perform the file upload. Make sure you apply this fix first.
User authentication
There are 2 methods that you can use:
BAA
Basic Access Authentication is an authentication method that is supported with all browsers.
When a resource is protected by BAA the webserver will send back to the client first: 401 - Authentication Required
Upon receiving this the browser will popup a window requiring the USER/PASSWORD from the client.
Because HTTP is stateless the browser will be responsable for prepending the user/pass in all requests (URL) for the given browsing session.
The user credentials are sent in plain text so this is not a safe communication method, the password can be retrieved easily with a sniffer. If security is important make sure you use SSL or DAA.
DAA
Digest Access Authentication is more secure because it's using a hashing scheme to send user/pass credential.
It should work correctly with all modern browsers.
Configuration instructions:
- recompile the WebServer to include the DAA code - in the Linux build (for example), you need to make sure that the Digest Access Switch macro (DASW) in the Makefile is set to -DDIGEST_ACCESS_SUPPORT
- configure the WebServer so that any directories or other resources you wish to require DAA login for are set to require that level of login.
For example, if you want everything in a directory named admin/ to be restricted to members of the admin login group, and require that they use DAA, place a call like:umAddAccessLimit(T("admin/", AM_DIGEST, 0, T("admin"));
in the initialization code for the WebServer.
IMPORTANT:
- If you enable DAA support in your code the webserver still accepts BAA.
This is an unsolved issue and the webserver code needs to be changed to support only one of these methods. - The webserver code needs to be patched for this feature to work correctly.
For more information take a look at the patches section.
Add users to different groups
Brett G. Porter, 2002/01/17
At this moment it is not possible to make a user part of multiple groups.
A solution is to add hierarchical security using numbers as group names where a higher number means that members on that group are more privileged.
The checking user credentials is done in um.c:umUserCanAccessUrl():
/* * If Access Limit has a group specified, then the user must be a * member of that group */ if (group && *group) { if (usergroup && (gstrcmp(group, usergroup) != 0)) { return FALSE; } }
We're doing a lexicographic compare to see if the user's group is an exact match for the group assigned to the URL.
To make this hierarchical with a minimum of work use numerical group names instead of symbolic and change the above code to something like:
/* * If Access Limit has a group specified, then the user must have an * access level that high or higher. */ if (group && *group) { if (usergroup) { // if this URL requires a higher access level than this // user has, deny them access. if (gatoi(group) > gatoi(usergroup)) return FALSE; } }
Assign:
- users to group = “1”
- techs to group = “2”
- admin to group = “3”
I've worked on systems with 255 levels of access available, but never ended up using more than 3. If you're more motivated, you might think about modifying the code so that you can use a symbolic group name and assign an access level to the group name.
Common problems
In case you made the necessary changes and the authentication is still not working try out the following:
- go to Internet Settings and clear the history.
- delete any temporary internet files.
- try access webs from another host because “localhost” is treated different.
For more information search forWEBS_LOCAL_REQUESTflag in the webserver code. - restart the server.
How to webrom files
Posted by Null Peiler, 2004/09/09
In case the documentation does not explain clearly how to perform this step, here is another tutorial:
- create a directory on your harddisk e.g. : mypages
- create a directory images in mypages
- copy your images into the directory images
- copy your html files into directory mypages
- copy webcomp.exe into directory mypages
- create a filelist.txt containing the relative paths to all html and asp pages you want to place in ROM
- add the relative paths for the images in filelist.txt e.g:
home.asp welcome.htm images/myfirstimage.gif images/mysecondimage.jpg ... - call webcomp.exe with filelist.txt as parameter and let webcomp create a file call mypages.c
- compile mypages.c and link it into your prog, the define WEBS_PAGE_ROM must be set when compiling.
By default, GoAhead WebServer retrieves Web pages from a file system. To force GoAhead WebServer to retrieve Web pages from ROM, set the following compiler
#define WEBS_PAGE_ROM
in the Makefile for your target operating system.
SSL
Which library to use?
Free
- Open SSL is a free library known to work with GoAhead and can be compiled on a large number of platforms.
- Build files available for all platforms.
- It was not designed for embedded usage so you may have problems with the footprint of the webserver.
Commercial
- Matrix SSL available under a dual license GNU/GPL and commercial. Built for embedded environments.
- Not integrated with webserver code, requires changes described here.
- The GPL license is incompatible with the GoAhead webserver license. If you plan to build against GoAhead you will have to get a commercial license.
- Mocana designed for embedded environments.
- Partial integration with the latest release of GoAhead webserver, makefiles available only for the Windows platform.
- BSAFE from RSA.
How to create a certificate
John Reynolds, Loren, Gabriel Petchesi 11/01/05
You can use two type of certificates:
- self-signed - it's free. The downside is that the client will be prompted to accept a certificate signed by an authority not known to the browser.
Usually the client browser will have to accept the certificate only once and it will not be prompted further. In Firefox you will be greeted by this image:
- signed by a Certification Authority can be expensive >500 USD, the certificate will be sent out to a CA for signing.
The main advantage is that the client will not be prompted to accept a certificate.
We explain here how to generate a self signed certificate for the GoAhead web server using openssl and the Linux shell.
1. Create a key and X.509 certificate:
$openssl req -x509 -newkey rsa:2048 -days 1024 -keyout server-key.pem -out server-cert.pem
The options that can be changed here are:
- the PK algorithm can be changed from rsa to dsa and also the length of the key in bits (512, 1024, 2048, 4096).
- time period for the certificate validity, we set it to 1024 days which is less than 3 years.
You can also set start/end date for the validity of the certificate.
You will be prompted for the PEM pass phrase twice for the key and than you have to enter some information necessary for the certificate:
Country Name <US> RO State or Province Name <YourState> Bihor City or Locality <Anchorage> Oradea Organization Name <Your business name> Prolix Organizational Unit leave blank -- just hit <enter> Software Development Common Name (SERVER HOST NAME) <www.yourdomain.dom> www.prolix.ro Server Admin's email address <you@yourdomain.dom> gabi@prolix.ro
It's important to specify correctly the Common Name. Wildcards can be used for domain name like *.prolix.ro for a certificate valid for all subdomains.
2. Strip pass phrase:
$openssl rsa -in server-key.pem -out server-key-nopassword.pem
3. Combine the key and X.509 certificate files into server.pem:
$cat server-key-nopassword.pem server-cert.pem > server.pem
4. Move them relative to the directory where the webs is started, in case of the LINUX build that is:
server.pem -> LINUX/server.pem server-cert.pem -> LINUX/certs/cacert.pem server-key.pem -> LINUX/certs/cakey.pem
The certitificate files are searched relative to the current directory. You must first change directory to LINUX and than start up webs.
If you are in another dir you get the following error:
SSL: Initializing SSL SSL: Unable to set cert verification locations! SSL: Closing SSL
What to do on a platform without a filesystem (VxWorks) ?
Published originally by Lee Dilkie:
- port SSL to VxWorks
- add the certificates and private keys (as data arrays, encoded in ASN.1) to the source itself and use openssl functions to import the data from arrays instead of using files.
- First you have to export the base64 encoded files into files containing 'C' data declarations:
openssl x509 -C -in my_cert_and_public_key_file.pem >cert.c
- Then for your private key (unfortunately they didn't add a -C option) do it in two steps:
openssl rsa -inform PEM -outform DER -in my_private_key_file.pem >key.der bintoc key.der dey.c
The source code for bintoc binary: bintoc.cpp stdafx.h needed to compile the app on Windows.
- then in websSSL.c modify the websSSLSetCertStuff, see this file for details: lee_dilkie.c
- there are some problems (exception in openssl when using RAND functions) unless you apply this patch hclu.patch.c
POST here your experiences after applying these changes.
SSI
Server Side Includes is a method to include content from other pages giving to a site a consistent look.
It's often used to have the same header/footer for all webpages.
This feature is not supported by the GoAhead webserver. See the following section for methods to simulate this.
Simulate using ASP
Shahid Mahmood, 2001/10/15
SSI features can be accoumplished using ASP. Here is what I do:
- on top of your .asp document, create a variable, eg.
<%myVar=1234;%>
- immediately following it, call your user defined function to update the value of that variable, eg
<%asp_updateMyVar();%>
- finally, write asp_updateMyVar() to modify the myVar. It must be defined in either main.c, the same way as aspTest() is defined there, e.g.
websAspDefine(T("aspTest"), aspTest);
It can be in main.c or or in a new file (I have a new file for all my asp functions). The asp_updateMyVar() must follow same prototype as aspTest().
include()
Robert Light, 2000/11/09
Define an ASP function to include the content of another file like in the following example:
<BODY><H1><%include('otherfile');%></H1><BODY>
Implementation:
int include( int eid, webs_t wp, int argc, char_t **argv) { char* path; char lpath[512]; if (ejArgs(argc, argv, T("%s"), &path) < 1) { websError(wp, 400, T("Insufficient args\n")); return -1; } sprintf(lpath,"%s/%s",websGetDefaultDir(),path); if (websPageOpen(wp, lpath, path, O_RDONLY | O_BINARY, 0666) < 0) { websError(wp, 400, T("Cannot open include file: <b>%s</b>"), path); return 1; } if (websAspRequest(wp, lpath) < 0) return 1; websPageClose(wp); return 1; }
Change default
Port
The port number is defined in the main.c file of your platform (port variable) and by default it's value is 80 (http port).
For UNIX change this value to something bigger than 1024 (8080) so that users will not need root access to start up the webserver.
Web Root
The rootWeb variable from main.c defines the dir where the html files are looked up. This a value relative to the current working directory. If you want to set up an absolute path like:
static char_t *rootWeb = T("/var/local/webpages"); /* Root web directory */
you also need to make another change in main.c:
/* * Set ../web as the root web. Modify this to suit your needs */ + /* getcwd(dir, sizeof(dir)); if ((cp = strrchr(dir, '/'))) { *cp = '\0'; } sprintf(webdir, "%s/%s", dir, rootWeb); + */ + sprintf(webdir, "%s", rootWeb);
Support "Status","Location" like Apache in CGI scripts
Report by newzy@hotmail.com, 2006/3/1
http://www.eybuild.com
I like write CGI with C with the tool ”eybuild”(http://www.eybuild.com), in which insert C code HTML directly(also call CSP). But when I write CGI and run in GoAhead, I found GoAhead doesn't support “Status”, “Location” in MIME header. So I porting this part from mini-httpd server.
To support “Status”, “Location” like apache in GoAhead CGI Script, in web.c websCgiGatherOutput (cgiRec *cgip) I change following:
+ glseek(fdout, cgip->fplacemark, SEEK_SET); /* move to here */
if (cgip->fplacemark == 0) {
+ struct web_cgi web_cgi = {fdout, cgip, wp};
+ cgi_interpose_output(&web_cgi , TRUE);
- websWrite(wp, T("HTTP/1.0 200 OK\r\n"));
}
- glseek(fdout, cgip->fplacemark, SEEK_SET);
Add following code before function websCgiGatherOutput()(NOTE most of following come from mini-httpd):
static void*
e_malloc( size_t size )
{
void* ptr;
ptr = malloc( size );
if ( ptr == (void*) 0 )
{
//syslog( LOG_CRIT, "out of memory" );
//(void) fprintf( stderr, "%s: out of memory\n", argv0 );
exit( 1 );
}
return ptr;
}
static void*
e_realloc( void* optr, size_t size )
{
void* ptr;
ptr = realloc( optr, size );
if ( ptr == (void*) 0 )
{
//syslog( LOG_CRIT, "out of memory" );
//(void) fprintf( stderr, "%s: out of memory\n", argv0 );
exit( 1 );
}
return ptr;
}
static void
add_to_buf( char** bufP, size_t* bufsizeP, size_t* buflenP, char* str, size_t len )
{
if ( *bufsizeP == 0 )
{
*bufsizeP = len + 500;
*buflenP = 0;
*bufP = (char*) e_malloc( *bufsizeP );
}
else if ( *buflenP + len >= *bufsizeP )
{
*bufsizeP = *buflenP + len + 500;
*bufP = (char*) e_realloc( (void*) *bufP, *bufsizeP );
}
(void) memmove( &((*bufP)[*buflenP]), str, len );
*buflenP += len;
(*bufP)[*buflenP] = '\0';
}
#define ssize_t int
#define snprintf _snprintf
struct web_cgi {
int fdout;
cgiRec * cgip;
webs_t wp;
};
/* This routine is used for parsed-header CGIs and for all SSL CGIs. */
static void
cgi_interpose_output(struct web_cgi * pcgi, int parse_headers )
{
ssize_t r;
char buf[1024];
int rfd = pcgi->fdout;
cgiRec * cgip = pcgi->cgip;
webs_t wp = pcgi->wp;
if ( ! parse_headers )
{
/* If we're not parsing headers, write out the default status line
** and proceed to the echo phase.
*/
char http_head[] = "HTTP/1.0 200 OK\015\012";
//(void) my_write( http_head, sizeof(http_head) );
(void) websWrite(wp, http_head);
}
else
{
/* Header parsing. The idea here is that the CGI can return special
** headers such as "Status:" and "Location:" which change the return
** status of the response. Since the return status has to be the very
** first line written out, we have to accumulate all the headers
** and check for the special ones before writing the status. Then
** we write out the saved headers and proceed to echo the rest of
** the response.
*/
size_t headers_size, headers_len;
char* headers;
char* br;
int status;
char* title;
char* cp;
/* Slurp in all headers. */
headers_size = 0;
add_to_buf( &headers, &headers_size, &headers_len, (char*) 0, 0 );
for (;;)
{
r = read( rfd, buf, sizeof(buf) );
if ( r <= 0 )
{
br = &(headers[headers_len]);
break;
}
add_to_buf( &headers, &headers_size, &headers_len, buf, r );
if ( ( br = strstr( headers, "\015\012\015\012" ) ) != (char*) 0 ||
( br = strstr( headers, "\012\012" ) ) != (char*) 0 )
break;
}
/* If there were no headers, bail. */
if ( headers[0] == '\0' )
return;
/* Figure out the status. */
status = 200;
if ( ( cp = strstr( headers, "Status:" ) ) != (char*) 0 &&
cp < br &&
( cp == headers || *(cp-1) == '\012' ) )
{
cp += 7;
cp += strspn( cp, " \t" );
status = atoi( cp );
}
if ( ( cp = strstr( headers, "Location:" ) ) != (char*) 0 &&
cp < br &&
( cp == headers || *(cp-1) == '\012' ) )
status = 302;
/* Write the status line. */
switch ( status )
{
case 200: title = "OK"; break;
case 302: title = "Found"; break;
case 304: title = "Not Modified"; break;
case 400: title = "Bad Request"; break;
case 401: title = "Unauthorized"; break;
case 403: title = "Forbidden"; break;
case 404: title = "Not Found"; break;
case 408: title = "Request Timeout"; break;
case 500: title = "Internal Error"; break;
case 501: title = "Not Implemented"; break;
case 503: title = "Service Temporarily Overloaded"; break;
default: title = "Something"; break;
}
(void) snprintf(
buf, sizeof(buf), "HTTP/1.0 %d %s\015\012", status, title );
//(void) my_write( buf, strlen( buf ) );
//websWrite(wp, buf);
websWriteBlock(wp, buf, strlen(buf));
/* Write the saved headers. */
//(void) my_write( headers, headers_len );
websWriteBlock(wp, headers, headers_len);
cgip->fplacemark += headers_len;
}
}