Contiki-NG
Loading...
Searching...
No Matches
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
71static struct httpd_ws_state conns[CONNS];
72
73PROCESS(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
80uint16_t http_connections = 0;
81
82static const char http_10[] = " HTTP/1.0\r\n";
83static const char http_content_type[] = "Content-Type:";
84static const char http_content_type_html[] = "text/html";
85static const char http_content_len[] = "Content-Length:";
86static const char http_header_404[] =
87 "HTTP/1.0 404 Not found\r\nServer: Contiki\r\nConnection: close\r\n";
88static const char http_header_200[] =
89 "HTTP/1.0 200 OK\r\nServer: Contiki\r\nConnection: close\r\n";
90static const char html_not_found[] =
91 "<html><body><h1>Page not found</h1></body></html>";
92/*---------------------------------------------------------------------------*/
93/* just set all states to unused */
94static void
95httpd_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/*---------------------------------------------------------------------------*/
104static struct httpd_ws_state *
105httpd_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/*---------------------------------------------------------------------------*/
120static
121PT_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/*---------------------------------------------------------------------------*/
130static
131PT_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/*---------------------------------------------------------------------------*/
146static
147PT_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/*---------------------------------------------------------------------------*/
174static
175PT_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/*---------------------------------------------------------------------------*/
226static
227PT_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/*---------------------------------------------------------------------------*/
284static void
285handle_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/*---------------------------------------------------------------------------*/
296void
297httpd_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/*---------------------------------------------------------------------------*/
355void
356httpd_ws_init(void)
357{
359 httpd_state_init();
360#if URLCONV
361 urlconv_init();
362#endif /* URLCONV */
363}
364/*---------------------------------------------------------------------------*/
365struct httpd_ws_state *
366httpd_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
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/*---------------------------------------------------------------------------*/
436PROCESS_THREAD(httpd_ws_process, ev, data)
437{
438 static struct etimer et;
439 int i;
440
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/*---------------------------------------------------------------------------*/
#define CLOCK_SECOND
A second, measured in system clock time.
Definition clock.h:103
void etimer_reset(struct etimer *et)
Reset an event timer with the same interval as was previously set.
Definition etimer.c:192
static bool etimer_expired(struct etimer *et)
Check if an event timer has expired.
Definition etimer.h:201
void etimer_set(struct etimer *et, clock_time_t interval)
Set an event timer.
Definition etimer.c:177
#define PROCESS(name, strname)
Declare a process.
Definition process.h:307
#define PROCESS_BEGIN()
Define the beginning of a process.
Definition process.h:120
#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 PROCESS_END()
Define the end of a process.
Definition process.h:131
#define PROCESS_CONTEXT_BEGIN(p)
Switch context to another process.
Definition process.h:426
#define PROCESS_THREAD(name, ev, data)
Define the body of a process.
Definition process.h:273
#define PROCESS_CONTEXT_END(p)
End a context switch.
Definition process.h:440
#define PSOCK_INIT(psock, buffer, buffersize)
Initialize a protosocket.
Definition psock.h:149
#define PSOCK_READBUF_LEN(psock, len)
Read data until at least len bytes have been read.
Definition psock.h:272
#define PSOCK_END(psock)
Declare the end of a protosocket's protothread.
Definition psock.h:347
#define PSOCK_DATALEN(psock)
The length of the data that was previously read.
Definition psock.h:303
#define PSOCK_CLOSE(psock)
Close a protosocket.
Definition psock.h:240
#define PSOCK_BEGIN(psock)
Start the protosocket protothread in a function.
Definition psock.h:163
#define PSOCK_READTO(psock, c)
Read data up to a specified character.
Definition psock.h:290
#define PSOCK_CLOSE_EXIT(psock)
Close a protosocket and exit the protosocket's protothread.
Definition psock.h:330
#define PT_WAIT_THREAD(pt, thread)
Block and wait until a child protothread completes.
Definition pt.h:357
#define PT_BEGIN(pt)
Declare the start of a protothread inside the C function implementing the protothread.
Definition pt.h:280
#define PT_THREAD(name_args)
Declaration of a protothread.
Definition pt.h:265
#define PT_END(pt)
Declare the end of a protothread.
Definition pt.h:292
#define PT_EXIT(pt)
Exit the protothread.
Definition pt.h:411
#define PT_WAIT_UNTIL(pt, condition)
Block and wait until condition is true.
Definition pt.h:313
#define PT_INIT(pt)
Initialize a protothread.
Definition pt.h:245
process_event_t tcpip_event
The uIP event.
Definition tcpip.c:62
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.
void tcp_listen(uint16_t port)
Open a TCP port.
Definition tcpip.c:232
void timer_set(struct timer *t, clock_time_t interval)
Set a timer.
Definition timer.c:64
void timer_restart(struct timer *t)
Restart the timer from the current point in time.
Definition timer.c:106
bool timer_expired(struct timer *t)
Check if a timer has expired.
Definition timer.c:123
#define uiplib_ipaddrconv
Convert a textual representation of an IP address to a numerical representation.
Definition uiplib.h:71
#define uip_poll()
Is the connection being polled by uIP?
Definition uip.h:759
#define uip_close()
Close the current connection.
Definition uip.h:613
#define uip_timedout()
Has the connection timed out?
Definition uip.h:733
#define uip_abort()
Abort the current connection.
Definition uip.h:624
#define uip_connected()
Has the connection just been connected?
Definition uip.h:703
#define uip_closed()
Has the connection been closed by the other end?
Definition uip.h:713
#define uip_aborted()
Has the connection been aborted by the other end?
Definition uip.h:723
#define UIP_HTONS(n)
Convert 16-bit quantity from host byte order to network byte order.
Definition uip.h:1157
uint16_t uip_htons(uint16_t val)
Convert a 16-bit quantity from host byte order to network byte order.
Definition uip6.c:2341
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:1263
A simple webserver for web services.
@ RESOLV_STATUS_CACHED
Hostname is fresh and usable.
Definition resolv.h:54
A timer.
Definition etimer.h:79
A timer.
Definition timer.h:84
Representation of a uIP TCP connection.
Definition uip.h:1258
static uip_ipaddr_t ipaddr
Pointer to prefix information option in uip_buf.
Definition uip-nd6.c:116
static uip_ds6_addr_t * addr
Pointer to a nbr cache entry.
Definition uip-nd6.c:107