Contiki-NG
httpd-ws.c
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2010-2012, Swedish Institute of Computer Science.
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  * notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  * notice, this list of conditions and the following disclaimer in the
12  * documentation and/or other materials provided with the distribution.
13  * 3. Neither the name of the Institute nor the names of its contributors
14  * may be used to endorse or promote products derived from this software
15  * without specific prior written permission.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
18  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20  * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
21  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27  * SUCH DAMAGE.
28  *
29  * This file is part of the Contiki operating system.
30  */
31 
32 /**
33  * \file
34  * A simple webserver for web services
35  * \author
36  * Adam Dunkels <adam@sics.se>
37  * Niclas Finne <nfi@sics.se>
38  * Joakim Eriksson <joakime@sics.se>
39  */
40 
41 #include <stdio.h>
42 #include <string.h>
43 #include <stdlib.h>
44 
45 #include "contiki-net.h"
46 #include "httpd-ws.h"
47 
48 #define DEBUG 0
49 #if DEBUG
50 #define PRINTF(...) printf(__VA_ARGS__)
51 #else
52 #define PRINTF(...)
53 #endif
54 
55 #ifndef WEBSERVER_CONF_CFS_CONNS
56 #define CONNS UIP_TCP_CONNS
57 #else /* WEBSERVER_CONF_CFS_CONNS */
58 #define CONNS WEBSERVER_CONF_CFS_CONNS
59 #endif /* WEBSERVER_CONF_CFS_CONNS */
60 
61 #ifndef WEBSERVER_CONF_CFS_URLCONV
62 #define URLCONV 0
63 #else /* WEBSERVER_CONF_CFS_URLCONV */
64 #define URLCONV WEBSERVER_CONF_CFS_URLCONV
65 #endif /* WEBSERVER_CONF_CFS_URLCONV */
66 
67 #if URLCONV
68 #include "urlconv.h"
69 #endif /* URLCONV */
70 
71 static struct httpd_ws_state conns[CONNS];
72 
73 PROCESS(httpd_ws_process, "Web server (WS)");
74 
75 #define ISO_nl 0x0a
76 #define ISO_space 0x20
77 #define ISO_period 0x2e
78 #define ISO_slash 0x2f
79 
80 uint16_t http_connections = 0;
81 
82 static const char http_10[] = " HTTP/1.0\r\n";
83 static const char http_content_type[] = "Content-Type:";
84 static const char http_content_type_html[] = "text/html";
85 static const char http_content_len[] = "Content-Length:";
86 static const char http_header_404[] =
87  "HTTP/1.0 404 Not found\r\nServer: Contiki\r\nConnection: close\r\n";
88 static const char http_header_200[] =
89  "HTTP/1.0 200 OK\r\nServer: Contiki\r\nConnection: close\r\n";
90 static const char html_not_found[] =
91  "<html><body><h1>Page not found</h1></body></html>";
92 /*---------------------------------------------------------------------------*/
93 /* just set all states to unused */
94 static void
95 httpd_state_init(void)
96 {
97  int i;
98 
99  for(i = 0; i < CONNS; i++) {
100  conns[i].state = HTTPD_WS_STATE_UNUSED;
101  }
102 }
103 /*---------------------------------------------------------------------------*/
104 static struct httpd_ws_state *
105 httpd_state_alloc(void)
106 {
107  int i;
108 
109  for(i = 0; i < CONNS; i++) {
110  if(conns[i].state == HTTPD_WS_STATE_UNUSED) {
111  conns[i].state = HTTPD_WS_STATE_INPUT;
112  return &conns[i];
113  }
114  }
115  return NULL;
116 }
117 /*---------------------------------------------------------------------------*/
118 #define httpd_state_free(s) (s->state = HTTPD_WS_STATE_UNUSED)
119 /*---------------------------------------------------------------------------*/
120 static
121 PT_THREAD(send_string(struct httpd_ws_state *s, const char *str, uint16_t len))
122 {
123  PSOCK_BEGIN(&s->sout);
124 
125  SEND_STRING(&s->sout, str, len);
126 
127  PSOCK_END(&s->sout);
128 }
129 /*---------------------------------------------------------------------------*/
130 static
131 PT_THREAD(send_headers(struct httpd_ws_state *s, const char *statushdr))
132 {
133  PSOCK_BEGIN(&s->sout);
134 
135  SEND_STRING(&s->sout, statushdr, strlen(statushdr));
136  s->outbuf_pos = snprintf(s->outbuf, sizeof(s->outbuf),
137  "%s %s\r\n\r\n", http_content_type,
138  s->content_type == NULL
139  ? http_content_type_html : s->content_type);
140  SEND_STRING(&s->sout, s->outbuf, s->outbuf_pos);
141  s->outbuf_pos = 0;
142 
143  PSOCK_END(&s->sout);
144 }
145 /*---------------------------------------------------------------------------*/
146 static
147 PT_THREAD(handle_output(struct httpd_ws_state *s))
148 {
149  PT_BEGIN(&s->outputpt);
150 
151  s->content_type = http_content_type_html;
152  s->script = httpd_ws_get_script(s);
153  if(s->script == NULL) {
154  PT_WAIT_THREAD(&s->outputpt, send_headers(s, http_header_404));
155  PT_WAIT_THREAD(&s->outputpt,
156  send_string(s, html_not_found, strlen(html_not_found)));
157  uip_close();
158 /* webserver_log_file(&uip_conn->ripaddr, "404 - not found"); */
159  PT_EXIT(&s->outputpt);
160  } else {
161  if(s->request_type == HTTPD_WS_POST) {
162  /* A post has a body that needs to be read */
163  s->state = HTTPD_WS_STATE_INPUT;
164  PT_WAIT_UNTIL(&s->outputpt, s->state == HTTPD_WS_STATE_OUTPUT);
165  }
166  PT_WAIT_THREAD(&s->outputpt, send_headers(s, http_header_200));
167  PT_WAIT_THREAD(&s->outputpt, s->script(s));
168  }
169  s->script = NULL;
170  PSOCK_CLOSE(&s->sout);
171  PT_END(&s->outputpt);
172 }
173 /*---------------------------------------------------------------------------*/
174 static
175 PT_THREAD(handle_request(struct httpd_ws_state *s))
176 {
177  PT_BEGIN(&s->outputpt);
178 
179  /* send the request line */
180  PT_WAIT_THREAD(&s->outputpt,
181  send_string(s, s->filename, strlen(s->filename)));
182  /* send host */
183  if(s->outbuf_pos > 0) {
184  PT_WAIT_THREAD(&s->outputpt, send_string(s, s->outbuf, s->outbuf_pos));
185  }
186 
187  if(s->content_type != NULL) {
188  s->outbuf_pos = snprintf(s->outbuf, sizeof(s->outbuf), "%s %s\r\n",
189  http_content_type, s->content_type);
190  PT_WAIT_THREAD(&s->outputpt, send_string(s, s->outbuf, s->outbuf_pos));
191  }
192  /* send the extra header(s) */
193  if(s->output_extra_headers != NULL) {
194  s->response_index = 0;
195  while((s->outbuf_pos =
196  s->output_extra_headers(s,
197  s->outbuf, sizeof(s->outbuf),
198  s->response_index)) > 0) {
199  PT_WAIT_THREAD(&s->outputpt, send_string(s, s->outbuf, s->outbuf_pos));
200  s->response_index++;
201  }
202  }
203 
204  /* send content length */
205  if(s->content_len > 0) {
206  s->outbuf_pos = snprintf(s->outbuf, sizeof(s->outbuf), "%s %u\r\n",
207  http_content_len, s->content_len);
208  }
209  /* send header separator */
210  if(s->outbuf_pos + 2 < sizeof(s->outbuf)) {
211  s->outbuf[s->outbuf_pos++] = '\r';
212  s->outbuf[s->outbuf_pos++] = '\n';
213  }
214  PT_WAIT_THREAD(&s->outputpt, send_string(s, s->outbuf, s->outbuf_pos));
215  s->outbuf_pos = 0;
216 
217  if(s->script != NULL) {
218  PT_WAIT_THREAD(&s->outputpt, s->script(s));
219  }
220  s->state = HTTPD_WS_STATE_REQUEST_INPUT;
221 
222  PSOCK_CLOSE(&s->sout);
223  PT_END(&s->outputpt);
224 }
225 /*---------------------------------------------------------------------------*/
226 static
227 PT_THREAD(handle_input(struct httpd_ws_state *s))
228 {
229  PSOCK_BEGIN(&s->sin);
230  PSOCK_READTO(&s->sin, ISO_space);
231 
232  if(strncmp(s->inputbuf, "GET ", 4) == 0) {
233  s->request_type = HTTPD_WS_GET;
234  } else if(strncmp(s->inputbuf, "POST ", 5) == 0) {
235  s->request_type = HTTPD_WS_POST;
236  s->content_len = 0;
237  } else if(strncmp(s->inputbuf, "HTTP ", 5) == 0) {
238  s->request_type = HTTPD_WS_RESPONSE;
239  } else {
240  PSOCK_CLOSE_EXIT(&s->sin);
241  }
242  PSOCK_READTO(&s->sin, ISO_space);
243 
244  /* TODO handle HTTP response */
245 
246  if(s->inputbuf[0] != ISO_slash) {
247  PSOCK_CLOSE_EXIT(&s->sin);
248  }
249 
250 #if URLCONV
251  s->inputbuf[PSOCK_DATALEN(&s->sin) - 1] = 0;
252  urlconv_tofilename(s->filename, s->inputbuf, sizeof(s->filename));
253 #else /* URLCONV */
254  s->inputbuf[PSOCK_DATALEN(&s->sin) - 1] = 0;
255  snprintf(s->filename, sizeof(s->filename), "%s", s->inputbuf);
256 #endif /* URLCONV */
257 
258 /* webserver_log_file(&uip_conn->ripaddr, s->filename); */
259  s->state = HTTPD_WS_STATE_OUTPUT;
260 
261  while(1) {
262  PSOCK_READTO(&s->sin, ISO_nl);
263 
264  if(s->request_type == HTTPD_WS_POST &&
265  strncmp(s->inputbuf, http_content_len, 15) == 0) {
266  s->inputbuf[PSOCK_DATALEN(&s->sin) - 2] = 0;
267  s->content_len = atoi(&s->inputbuf[16]);
268  }
269 
270  /* should have a header callback here check_header(s) */
271 
272  if(PSOCK_DATALEN(&s->sin) > 2) {
273  s->inputbuf[PSOCK_DATALEN(&s->sin) - 2] = 0;
274  } else if(s->request_type == HTTPD_WS_POST) {
275  PSOCK_READBUF_LEN(&s->sin, s->content_len);
276  s->inputbuf[PSOCK_DATALEN(&s->sin)] = 0;
277  /* printf("Content: '%s'\nSize:%d\n", s->inputbuf, PSOCK_DATALEN(&s->sin)); */
278  s->state = HTTPD_WS_STATE_OUTPUT;
279  }
280  }
281  PSOCK_END(&s->sin);
282 }
283 /*---------------------------------------------------------------------------*/
284 static void
285 handle_connection(struct httpd_ws_state *s)
286 {
287  if(s->state == HTTPD_WS_STATE_REQUEST_OUTPUT) {
288  handle_request(s);
289  }
290  handle_input(s);
291  if(s->state == HTTPD_WS_STATE_OUTPUT) {
292  handle_output(s);
293  }
294 }
295 /*---------------------------------------------------------------------------*/
296 void
297 httpd_ws_appcall(void *state)
298 {
299  struct httpd_ws_state *s = (struct httpd_ws_state *)state;
300 
301  if(uip_closed() || uip_aborted() || uip_timedout()) {
302  if(s != NULL) {
303  PRINTF("HTTPD-WS: closed/aborted (%d)\n", http_connections);
304  http_connections--;
305  httpd_state_free(s);
306  } else {
307  PRINTF("HTTPD-WS: closed/aborted ** NO HTTPD_WS_STATE!!! ** (%d)\n",
308  http_connections);
309  }
310  } else if(uip_connected()) {
311  if(s == NULL) {
312  s = httpd_state_alloc();
313  if(s == NULL) {
314  uip_abort();
315  PRINTF("HTTPD-WS: aborting - no resource (%d)\n", http_connections);
316  /* webserver_log_file(&uip_conn->ripaddr, "reset (no memory block)"); */
317  return;
318  }
319  http_connections++;
320 
321  tcp_markconn(uip_conn, s);
322  s->state = HTTPD_WS_STATE_INPUT;
323  } else {
324  /* this is a request that is to be sent! */
325  s->state = HTTPD_WS_STATE_REQUEST_OUTPUT;
326  }
327  PSOCK_INIT(&s->sin, (uint8_t *)s->inputbuf, sizeof(s->inputbuf) - 1);
328  PSOCK_INIT(&s->sout, (uint8_t *)s->inputbuf, sizeof(s->inputbuf) - 1);
329  PT_INIT(&s->outputpt);
330  timer_set(&s->timer, CLOCK_SECOND * 30);
331  handle_connection(s);
332  } else if(s != NULL) {
333  if(uip_poll()) {
334  if(timer_expired(&s->timer)) {
335  uip_abort();
336  PRINTF("HTTPD-WS: aborting - http timeout (%d)\n", http_connections);
337  http_connections--;
338  httpd_state_free(s);
339 /* webserver_log_file(&uip_conn->ripaddr, "reset (timeout)"); */
340  } else {
341  PRINTF("HTTPD-WS: uip-poll (%d)\n", http_connections);
342  }
343  } else {
344 /* PRINTF("HTTPD-WS: restart timer %s (%d)\n", s->filename, */
345 /* http_connections); */
346  timer_restart(&s->timer);
347  }
348  handle_connection(s);
349  } else {
350  PRINTF("HTTPD-WS: aborting - no state (%d)\n", http_connections);
351  uip_abort();
352  }
353 }
354 /*---------------------------------------------------------------------------*/
355 void
356 httpd_ws_init(void)
357 {
358  tcp_listen(UIP_HTONS(80));
359  httpd_state_init();
360 #if URLCONV
361  urlconv_init();
362 #endif /* URLCONV */
363 }
364 /*---------------------------------------------------------------------------*/
365 struct httpd_ws_state *
366 httpd_ws_request(char request_type, const char *host_ip, const char *host_hdr,
367  uint16_t port, const char *file,
368  const char *content_type, uint16_t content_len,
369  httpd_ws_script_t generator)
370 {
371  struct httpd_ws_state *s;
372  struct uip_conn *conn;
373  uip_ipaddr_t *ipaddr;
374  uip_ipaddr_t addr;
375  char *request_str;
376 
377  /* First check if the host is an IP address. */
378  ipaddr = &addr;
379  if(uiplib_ipaddrconv(host_ip, &addr) == 0) {
380 #if 0 && UIP_UDP
381  if(resolv_lookup(host, &ipaddr) != RESOLV_STATUS_CACHED) {
382  return NULL;
383  }
384 #else /* UIP_UDP */
385  return NULL;
386 #endif /* UIP_UDP */
387  }
388 
389  s = httpd_state_alloc();
390  if(s == NULL) {
391  /* no memory left... do no request... */
392  return NULL;
393  }
394  http_connections++;
395 
396  switch(request_type) {
397  case HTTPD_WS_POST:
398  request_str = "POST ";
399  break;
400  case HTTPD_WS_PUT:
401  request_str = "PUT ";
402  break;
403  default:
404  request_str = "GET ";
405  break;
406  }
407 
408  s->request_type = request_type;
409  s->content_len = content_len;
410  s->content_type = content_type;
411  s->script = generator;
412  s->state = HTTPD_WS_STATE_REQUEST_OUTPUT;
413 
414  /* create a request line for a POST - should check size of it!!! */
415  /* Assume post for now */
416  snprintf(s->filename, sizeof(s->filename), "%s%s%s",
417  request_str, file, http_10);
418  s->outbuf_pos = snprintf(s->outbuf, sizeof(s->outbuf), "Host:%s\r\n",
419  host_hdr != NULL ? host_hdr : host_ip);
420 
421  PROCESS_CONTEXT_BEGIN(&httpd_ws_process);
422  conn = tcp_connect(ipaddr, uip_htons(port), s);
423  PROCESS_CONTEXT_END(&httpd_ws_process);
424  if(conn == NULL) {
425  PRINTF("HTTPD-WS: aborting... could not allocate tcp connection (%d)\n",
426  http_connections);
427  httpd_state_free(s);
428  http_connections--;
429  return NULL;
430  }
431  PRINTF("HTTPD-WS: created http connection (%d)\n", http_connections);
432 
433  return s;
434 }
435 /*---------------------------------------------------------------------------*/
436 PROCESS_THREAD(httpd_ws_process, ev, data)
437 {
438  static struct etimer et;
439  int i;
440 
441  PROCESS_BEGIN();
442 
443  httpd_ws_init();
444 
445  PRINTF("Buffer size, input %d, output\n",
446  HTTPD_INBUF_SIZE, HTTPD_OUTBUF_SIZE);
447 
448  /* Delay 2-4 seconds */
449  etimer_set(&et, CLOCK_SECOND * 10);
450 
451  /* GC any http session that is too long lived - either because other
452  end never closed or if any other state cause too long lived http
453  sessions */
454  while(1) {
456  if(ev == tcpip_event) {
457  httpd_ws_appcall(data);
458  } else if(etimer_expired(&et)) {
459  PRINTF("HTTPD States: ");
460  for(i = 0; i < CONNS; i++) {
461  PRINTF("%d ", conns[i].state);
462  if(conns[i].state != HTTPD_WS_STATE_UNUSED &&
463  timer_expired(&conns[i].timer)) {
464  conns[i].state = HTTPD_WS_STATE_UNUSED;
465  PRINTF("\n*** RELEASED HTTPD Session\n");
466  http_connections--;
467  }
468  }
469  PRINTF("\n");
470  etimer_reset(&et);
471  }
472  }
473 
474  PROCESS_END();
475 }
476 /*---------------------------------------------------------------------------*/
static uip_ipaddr_t ipaddr
Pointer to prefix information option in uip_buf.
Definition: uip-nd6.c:125
#define PSOCK_INIT(psock, buffer, buffersize)
Initialize a protosocket.
Definition: psock.h:150
void timer_set(struct timer *t, clock_time_t interval)
Set a timer.
Definition: timer.c:64
#define PROCESS(name, strname)
Declare a process.
Definition: process.h:307
#define uip_close()
Close the current connection.
Definition: uip.h:662
#define PSOCK_CLOSE(psock)
Close a protosocket.
Definition: psock.h:241
Representation of a uIP TCP connection.
Definition: uip.h:1346
#define PROCESS_CONTEXT_END(p)
End a context switch.
Definition: process.h:440
Hostname is fresh and usable.
Definition: resolv.h:63
#define PSOCK_READBUF_LEN(psock, len)
Read data until at least len bytes have been read.
Definition: psock.h:273
static uip_ds6_addr_t * addr
Pointer to a nbr cache entry.
Definition: uip-nd6.c:116
#define uip_connected()
Has the connection just been connected?
Definition: uip.h:752
#define PROCESS_BEGIN()
Define the beginning of a process.
Definition: process.h:120
#define PROCESS_END()
Define the end of a process.
Definition: process.h:131
process_event_t tcpip_event
The uIP event.
Definition: tcpip.c:66
#define PROCESS_WAIT_EVENT_UNTIL(c)
Wait for an event to be posted to the process, with an extra condition.
Definition: process.h:157
#define PT_BEGIN(pt)
Declare the start of a protothread inside the C function implementing the protothread.
Definition: pt.h:114
#define PT_WAIT_UNTIL(pt, condition)
Block and wait until condition is true.
Definition: pt.h:147
#define PT_WAIT_THREAD(pt, thread)
Block and wait until a child protothread completes.
Definition: pt.h:191
A timer.
Definition: timer.h:82
#define PSOCK_BEGIN(psock)
Start the protosocket protothread in a function.
Definition: psock.h:164
uint16_t uip_htons(uint16_t val)
Convert a 16-bit quantity from host byte order to network byte order.
Definition: uip6.c:2324
#define PSOCK_DATALEN(psock)
The length of the data that was previously read.
Definition: psock.h:304
#define PT_INIT(pt)
Initialize a protothread.
Definition: pt.h:79
#define uip_abort()
Abort the current connection.
Definition: uip.h:673
#define CLOCK_SECOND
A second, measured in system clock time.
Definition: clock.h:82
#define PT_END(pt)
Declare the end of a protothread.
Definition: pt.h:126
resolv_status_t resolv_lookup(const char *name, uip_ipaddr_t **ipaddr)
Look up a hostname in the array of known hostnames.
Definition: resolv.c:1313
void timer_restart(struct timer *t)
Restart the timer from the current point in time.
Definition: timer.c:106
int timer_expired(struct timer *t)
Check if a timer has expired.
Definition: timer.c:123
int etimer_expired(struct etimer *et)
Check if an event timer has expired.
Definition: etimer.c:213
#define PSOCK_END(psock)
Declare the end of a protosocket&#39;s protothread.
Definition: psock.h:348
#define PT_EXIT(pt)
Exit the protothread.
Definition: pt.h:245
#define PT_THREAD(name_args)
Declaration of a protothread.
Definition: pt.h:99
A timer.
Definition: etimer.h:75
#define uip_aborted()
Has the connection been aborted by the other end?
Definition: uip.h:772
#define UIP_HTONS(n)
Convert 16-bit quantity from host byte order to network byte order.
Definition: uip.h:1230
#define PROCESS_CONTEXT_BEGIN(p)
Switch context to another process.
Definition: process.h:426
#define PSOCK_READTO(psock, c)
Read data up to a specified character.
Definition: psock.h:291
void tcp_listen(uint16_t port)
Open a TCP port.
Definition: tcpip.c:233
#define uip_timedout()
Has the connection timed out?
Definition: uip.h:782
#define uip_closed()
Has the connection been closed by the other end?
Definition: uip.h:762
#define uip_poll()
Is the connection being polled by uIP?
Definition: uip.h:808
PROCESS_THREAD(cc2538_rf_process, ev, data)
Implementation of the cc2538 RF driver process.
Definition: cc2538-rf.c:1035
#define uiplib_ipaddrconv
Convert a textual representation of an IP address to a numerical representation.
Definition: uiplib.h:72
#define PSOCK_CLOSE_EXIT(psock)
Close a protosocket and exit the protosocket&#39;s protothread.
Definition: psock.h:331
void etimer_reset(struct etimer *et)
Reset an event timer with the same interval as was previously set.
Definition: etimer.c:192
void etimer_set(struct etimer *et, clock_time_t interval)
Set an event timer.
Definition: etimer.c:177
struct uip_conn * tcp_connect(const uip_ipaddr_t *ripaddr, uint16_t port, void *appstate)
Open a TCP connection to the specified IP address and port.
A simple webserver for web services