2 * \addtogroup exampleapps
\r
7 * \defgroup httpd Web server
\r
10 * The uIP web server is a very simplistic implementation of an HTTP
\r
11 * server. It can serve web pages and files from a read-only ROM
\r
12 * filesystem, and provides a very small scripting language.
\r
14 * The script language is very simple and works as follows. Each
\r
15 * script line starts with a command character, either "i", "t", "c",
\r
16 * "#" or ".". The "i" command tells the script interpreter to
\r
17 * "include" a file from the virtual file system and output it to the
\r
18 * web browser. The "t" command should be followed by a line of text
\r
19 * that is to be output to the browser. The "c" command is used to
\r
20 * call one of the C functions from the httpd-cgi.c file. A line that
\r
21 * starts with a "#" is ignored (i.e., the "#" denotes a comment), and
\r
22 * the "." denotes the last script line.
\r
24 * The script that produces the file statistics page looks somewhat
\r
29 t <h1>File statistics</h1><br><table width="100%">
\r
30 t <tr><td><a href="/index.html">/index.html</a></td><td>
\r
32 t </td></tr> <tr><td><a href="/cgi/files">/cgi/files</a></td><td>
\r
34 t </td></tr> <tr><td><a href="/cgi/tcp">/cgi/tcp</a></td><td>
\r
36 t </td></tr> <tr><td><a href="/404.html">/404.html</a></td><td>
\r
38 t </td></tr></table>
\r
49 * \author Adam Dunkels <adam@dunkels.com>
\r
53 * Copyright (c) 2001, Adam Dunkels.
\r
54 * All rights reserved.
\r
56 * Redistribution and use in source and binary forms, with or without
\r
57 * modification, are permitted provided that the following conditions
\r
59 * 1. Redistributions of source code must retain the above copyright
\r
60 * notice, this list of conditions and the following disclaimer.
\r
61 * 2. Redistributions in binary form must reproduce the above copyright
\r
62 * notice, this list of conditions and the following disclaimer in the
\r
63 * documentation and/or other materials provided with the distribution.
\r
64 * 3. The name of the author may not be used to endorse or promote
\r
65 * products derived from this software without specific prior
\r
66 * written permission.
\r
68 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
\r
69 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
\r
70 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
\r
71 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
\r
72 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
\r
73 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
\r
74 * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
\r
75 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
\r
76 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
\r
77 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
\r
78 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\r
80 * This file is part of the uIP TCP/IP stack.
\r
82 * $Id: httpd.c,v 1.28.2.6 2003/10/07 13:22:27 adam Exp $
\r
93 #define NULL (void *)0
\r
95 /* The HTTP server states: */
\r
96 #define HTTP_NOGET 0
\r
111 struct httpd_state *hs;
\r
113 extern const struct fsdata_file file_index_html;
\r
114 extern const struct fsdata_file file_404_html;
\r
116 static void next_scriptline(void);
\r
117 static void next_scriptstate(void);
\r
122 #define ISO_slash 0x2f
\r
126 #define ISO_space 0x20
\r
127 #define ISO_nl 0x0a
\r
128 #define ISO_cr 0x0d
\r
131 #define ISO_hash 0x23
\r
132 #define ISO_period 0x2e
\r
134 #define httpPORT 80
\r
136 /*-----------------------------------------------------------------------------------*/
\r
138 * Initialize the web server.
\r
140 * Starts to listen for incoming connection requests on TCP port 80.
\r
142 /*-----------------------------------------------------------------------------------*/
\r
148 /* Listen to port 80. */
\r
149 uip_listen(HTONS(httpPORT));
\r
151 /*-----------------------------------------------------------------------------------*/
\r
153 httpd_appcall(void)
\r
155 struct fs_file fsfile;
\r
159 switch(uip_conn->lport) {
\r
160 /* This is the web server: */
\r
161 case HTONS(httpPORT):
\r
162 /* Pick out the application state from the uip_conn structure. */
\r
163 hs = (struct httpd_state *)(uip_conn->appstate);
\r
165 /* We use the uip_ test functions to deduce why we were
\r
166 called. If uip_connected() is non-zero, we were called
\r
167 because a remote host has connected to us. If
\r
168 uip_newdata() is non-zero, we were called because the
\r
169 remote host has sent us new data, and if uip_acked() is
\r
170 non-zero, the remote host has acknowledged the data we
\r
171 previously sent to it. */
\r
172 if(uip_connected()) {
\r
173 /* Since we have just been connected with the remote host, we
\r
174 reset the state for this connection. The ->count variable
\r
175 contains the amount of data that is yet to be sent to the
\r
176 remote host, and the ->state is set to HTTP_NOGET to signal
\r
177 that we haven't received any HTTP GET request for this
\r
180 hs->state = HTTP_NOGET;
\r
184 } else if(uip_poll()) {
\r
185 /* If we are polled ten times, we abort the connection. This is
\r
186 because we don't want connections lingering indefinately in
\r
188 if(hs->count++ >= 10) {
\r
192 } else if(uip_newdata() && hs->state == HTTP_NOGET) {
\r
193 /* This is the first data we receive, and it should contain a
\r
196 /* Check for GET. */
\r
197 if(uip_appdata[0] != ISO_G ||
\r
198 uip_appdata[1] != ISO_E ||
\r
199 uip_appdata[2] != ISO_T ||
\r
200 uip_appdata[3] != ISO_space) {
\r
201 /* If it isn't a GET, we abort the connection. */
\r
206 /* Find the file we are looking for. */
\r
207 for(i = 4; i < 40; ++i) {
\r
208 if(uip_appdata[i] == ISO_space ||
\r
209 uip_appdata[i] == ISO_cr ||
\r
210 uip_appdata[i] == ISO_nl) {
\r
211 uip_appdata[i] = 0;
\r
216 PRINT("request for file ");
\r
217 PRINTLN(&uip_appdata[4]);
\r
219 /* Check for a request for "/". */
\r
220 if(uip_appdata[4] == ISO_slash &&
\r
221 uip_appdata[5] == 0) {
\r
222 fs_open(file_index_html.name, &fsfile);
\r
224 if(!fs_open((const char *)&uip_appdata[4], &fsfile)) {
\r
225 PRINTLN("couldn't open file");
\r
226 fs_open(file_404_html.name, &fsfile);
\r
231 if(uip_appdata[4] == ISO_slash &&
\r
232 uip_appdata[5] == ISO_c &&
\r
233 uip_appdata[6] == ISO_g &&
\r
234 uip_appdata[7] == ISO_i &&
\r
235 uip_appdata[8] == ISO_slash) {
\r
236 /* If the request is for a file that starts with "/cgi/", we
\r
237 prepare for invoking a script. */
\r
238 hs->script = fsfile.data;
\r
239 next_scriptstate();
\r
242 /* The web server is now no longer in the HTTP_NOGET state, but
\r
243 in the HTTP_FILE state since is has now got the GET from
\r
244 the client and will start transmitting the file. */
\r
245 hs->state = HTTP_FILE;
\r
247 /* Point the file pointers in the connection state to point to
\r
248 the first byte of the file. */
\r
249 hs->dataptr = fsfile.data;
\r
250 hs->count = fsfile.len;
\r
255 if(hs->state != HTTP_FUNC) {
\r
256 /* Check if the client (remote end) has acknowledged any data that
\r
257 we've previously sent. If so, we move the file pointer further
\r
258 into the file and send back more data. If we are out of data to
\r
259 send, we close the connection. */
\r
261 if(hs->count >= uip_conn->len) {
\r
262 hs->count -= uip_conn->len;
\r
263 hs->dataptr += uip_conn->len;
\r
268 if(hs->count == 0) {
\r
269 if(hs->script != NULL) {
\r
271 next_scriptstate();
\r
278 /* Call the CGI function. */
\r
279 if(cgitab[hs->script[2] - ISO_a](uip_acked())) {
\r
280 /* If the function returns non-zero, we jump to the next line
\r
283 next_scriptstate();
\r
287 if(hs->state != HTTP_FUNC && !uip_poll()) {
\r
288 /* Send a piece of data, but not more than the MSS of the
\r
290 uip_send(hs->dataptr, hs->count);
\r
293 /* Finally, return to uIP. Our outgoing packet will soon be on its
\r
298 /* Should never happen. */
\r
303 /*-----------------------------------------------------------------------------------*/
\r
304 /* next_scriptline():
\r
306 * Reads the script until it finds a newline. */
\r
308 next_scriptline(void)
\r
310 /* Loop until we find a newline character. */
\r
313 } while(hs->script[0] != ISO_nl);
\r
315 /* Eat up the newline as well. */
\r
318 /*-----------------------------------------------------------------------------------*/
\r
319 /* next_sciptstate:
\r
321 * Reads one line of script and decides what to do next.
\r
324 next_scriptstate(void)
\r
326 struct fs_file fsfile;
\r
330 switch(hs->script[0]) {
\r
332 /* Send a text string. */
\r
333 hs->state = HTTP_TEXT;
\r
334 hs->dataptr = &hs->script[2];
\r
336 /* Calculate length of string. */
\r
337 for(i = 0; hs->dataptr[i] != ISO_nl; ++i);
\r
341 /* Call a function. */
\r
342 hs->state = HTTP_FUNC;
\r
343 hs->dataptr = NULL;
\r
345 cgitab[hs->script[2] - ISO_a](0);
\r
348 /* Include a file. */
\r
349 hs->state = HTTP_FILE;
\r
350 if(!fs_open(&hs->script[2], &fsfile)) {
\r
353 hs->dataptr = fsfile.data;
\r
354 hs->count = fsfile.len;
\r
357 /* Comment line. */
\r
362 /* End of script. */
\r
363 hs->state = HTTP_END;
\r
371 /*-----------------------------------------------------------------------------------*/
\r