/*$T httpd.c GC 1.138 07/23/05 13:10:49 */ /* * \addtogroup exampleapps @{ £ * \defgroup httpd Web server @{ The uIP web server is a very simplistic * implementation of an HTTP server. It can serve web pages and files from a * read-only ROM filesystem, and provides a very small scripting language. The * script language is very simple and works as follows. Each script line starts * with a command character, either "i", "t", "c", "#" or ".". The "i" command * tells the script interpreter to "include" a file from the virtual file system * and output it to the web browser. The "t" command should be followed by a line * of text that is to be output to the browser. The "c" command is used to call * one of the C functions from the httpd-cgi.c file. A line that starts with a "#" * is ignored (i.e., the "#" denotes a comment), and the "." denotes the last * script line. The script that produces the file statistics page looks somewhat * like this: \code i /header.html t

File statistics


t
/index.html c a * /index.html t
/cgi/files c * a /cgi/files t
/cgi/tcp c a * /cgi/tcp t
/404.html c a * /404.html t
i /footer.plain . \endcode £ * \file HTTP server. \author Adam Dunkels £ * Copyright (c) 2001, Adam Dunkels. All rights reserved. Redistribution and use * in source and binary forms, with or without modification, are permitted * provided that the following conditions are met: 1. Redistributions of source * code must retain the above copyright notice, this list of conditions and the * following disclaimer. 2. Redistributions in binary form must reproduce the * above copyright notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the distribution. 3. The * name of the author may not be used to endorse or promote products derived from * this software without specific prior written permission. THIS SOFTWARE IS * PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * This file is part of the uIP TCP/IP stack. $Id: httpd.c,v 1.28.2.6 2003/10/07 * 13:22:27 adam Exp $ */ #include "uip.h" #include "httpd.h" #include "fs.h" #include "fsdata.h" #include "cgi.h" #define NULL ( void * ) 0 /* The HTTP server states: */ #define HTTP_NOGET 0 #define HTTP_FILE 1 #define HTTP_TEXT 2 #define HTTP_FUNC 3 #define HTTP_END 4 #ifdef DEBUG #include #define PRINT( x ) #define PRINTLN( x ) #else /* DEBUG */ #define PRINT( x ) #define PRINTLN( x ) #endif /* DEBUG */ struct httpd_state *hs; extern const struct fsdata_file file_index_html; extern const struct fsdata_file file_404_html; static void next_scriptline( void ); static void next_scriptstate( void ); #define ISO_G 0x47 #define ISO_E 0x45 #define ISO_T 0x54 #define ISO_slash 0x2f #define ISO_c 0x63 #define ISO_g 0x67 #define ISO_i 0x69 #define ISO_space 0x20 #define ISO_nl 0x0a #define ISO_cr 0x0d #define ISO_a 0x61 #define ISO_t 0x74 #define ISO_hash 0x23 #define ISO_period 0x2e #define httpPORT 80 /* ======================================================================================================================= Initialize the web server. Starts to listen for incoming connection requests on TCP port 80. ======================================================================================================================= */ void httpd_init( void ) { fs_init(); /* Listen to port 80. */ uip_listen( HTONS( httpPORT ) ); } /* ======================================================================================================================= ======================================================================================================================= */ void httpd_appcall( void ) { /*~~~~~~~~~~~~~~~~~~~*/ struct fs_file fsfile; u8_t i; /*~~~~~~~~~~~~~~~~~~~*/ switch( uip_conn->lport ) { /* This is the web server: */ case HTONS( httpPORT ): /* Pick out the application state from the uip_conn structure. */ hs = ( struct httpd_state * ) ( uip_conn->appstate ); /* * We use the uip_ test functions to deduce why we were called. If uip_connected() * is non-zero, we were called because a remote host has connected to us. If * uip_newdata() is non-zero, we were called because the remote host has sent us * new data, and if uip_acked() is non-zero, the remote host has acknowledged the * data we previously sent to it. */ if( uip_connected() ) { /* * Since we have just been connected with the remote host, we reset the state for * this connection. The ->count variable contains the amount of data that is yet * to be sent to the remote host, and the ->state is set to HTTP_NOGET to signal * that we haven't received any HTTP GET request for this connection yet. */ hs->state = HTTP_NOGET; hs->count = 0; return; } else if( uip_poll() ) { /* * If we are polled ten times, we abort the connection. This is because we don't * want connections lingering indefinately in the system. */ if( hs->count++ >= 10 ) { uip_abort(); } return; } else if( uip_newdata() && hs->state == HTTP_NOGET ) { /* * This is the first data we receive, and it should contain a GET. £ * Check for GET. */ if ( uip_appdata[0] != ISO_G || uip_appdata[1] != ISO_E || uip_appdata[2] != ISO_T || uip_appdata[3] != ISO_space ) { /* If it isn't a GET, we abort the connection. */ uip_abort(); return; } /* Find the file we are looking for. */ for( i = 4; i < 40; ++i ) { if( uip_appdata[i] == ISO_space || uip_appdata[i] == ISO_cr || uip_appdata[i] == ISO_nl ) { uip_appdata[i] = 0; break; } } PRINT( "request for file " ); PRINTLN( &uip_appdata[4] ); /* Check for a request for "/". */ if( uip_appdata[4] == ISO_slash && uip_appdata[5] == 0 ) { fs_open( file_index_html.name, &fsfile ); } else { if( !fs_open( ( const char * ) &uip_appdata[4], &fsfile ) ) { PRINTLN( "couldn't open file" ); fs_open( file_404_html.name, &fsfile ); } } if ( uip_appdata[4] == ISO_slash && uip_appdata[5] == ISO_c && uip_appdata[6] == ISO_g && uip_appdata[7] == ISO_i && uip_appdata[8] == ISO_slash ) { /* * If the request is for a file that starts with "/cgi/", we prepare for invoking * a script. */ hs->script = fsfile.data; next_scriptstate(); } else { hs->script = NULL; /* * The web server is now no longer in the HTTP_NOGET state, but in the HTTP_FILE * state since is has now got the GET from the client and will start transmitting * the file. */ hs->state = HTTP_FILE; /* * Point the file pointers in the connection state to point to the first byte of * the file. */ hs->dataptr = fsfile.data; hs->count = fsfile.len; } } if( hs->state != HTTP_FUNC ) { /* * Check if the client (remote end) has acknowledged any data that we've * previously sent. If so, we move the file pointer further into the file and send * back more data. If we are out of data to send, we close the connection. */ if( uip_acked() ) { if( hs->count >= uip_conn->len ) { hs->count -= uip_conn->len; hs->dataptr += uip_conn->len; } else { hs->count = 0; } if( hs->count == 0 ) { if( hs->script != NULL ) { next_scriptline(); next_scriptstate(); } else { uip_close(); } } } } else { /* Call the CGI function. */ if( cgitab[hs->script[2] - ISO_a](uip_acked()) ) { /* If the function returns non-zero, we jump to the next line in the script. */ next_scriptline(); next_scriptstate(); } } if( hs->state != HTTP_FUNC && !uip_poll() ) { /* Send a piece of data, but not more than the MSS of the connection. */ uip_send( ( void * ) hs->dataptr, hs->count ); } /* Finally, return to uIP. Our outgoing packet will soon be on its way... */ return; default: /* Should never happen. */ uip_abort(); break; } } /* ======================================================================================================================= next_scriptline(): Reads the script until it finds a newline. ======================================================================================================================= */ static void next_scriptline( void ) { /* Loop until we find a newline character. */ do { ++( hs->script ); } while( hs->script[0] != ISO_nl ); /* Eat up the newline as well. */ ++( hs->script ); } /* ======================================================================================================================= next_sciptstate: Reads one line of script and decides what to do next. ======================================================================================================================= */ static void next_scriptstate( void ) { /*~~~~~~~~~~~~~~~~~~~*/ struct fs_file fsfile; u8_t i; /*~~~~~~~~~~~~~~~~~~~*/ again: switch( hs->script[0] ) { case ISO_t: /* Send a text string. */ hs->state = HTTP_TEXT; hs->dataptr = &hs->script[2]; /* Calculate length of string. */ for( i = 0; hs->dataptr[i] != ISO_nl; ++i ); hs->count = i; break; case ISO_c: /* Call a function. */ hs->state = HTTP_FUNC; hs->dataptr = NULL; hs->count = 0; cgitab[hs->script[2] - ISO_a]( 0 ); break; case ISO_i: /* Include a file. */ hs->state = HTTP_FILE; if( !fs_open( &hs->script[2], &fsfile ) ) { uip_abort(); } hs->dataptr = fsfile.data; hs->count = fsfile.len; break; case ISO_hash: /* Comment line. */ next_scriptline(); goto again; case ISO_period: /* End of script. */ hs->state = HTTP_END; uip_close(); break; default: uip_abort(); break; } } /* * @} £ * @} */