51b73127e9
* Simple HTTP webservice with support for both receiving and sending HTTP requests. * json-ws example that optionally push sensor data to COSM over IPv6.
478 lines
14 KiB
C
478 lines
14 KiB
C
/*
|
|
* Copyright (c) 2010-2012, Swedish Institute of Computer Science.
|
|
* 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. Neither the name of the Institute nor the names of its contributors
|
|
* may be used to endorse or promote products derived from this software
|
|
* without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``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 INSTITUTE OR CONTRIBUTORS 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 Contiki operating system.
|
|
*/
|
|
|
|
/**
|
|
* \file
|
|
* A simple webserver for web services
|
|
* \author
|
|
* Adam Dunkels <adam@sics.se>
|
|
* Niclas Finne <nfi@sics.se>
|
|
* Joakim Eriksson <joakime@sics.se>
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
|
|
#include "contiki-net.h"
|
|
#include "httpd-ws.h"
|
|
|
|
#define DEBUG 0
|
|
#if DEBUG
|
|
#define PRINTF(...) printf(__VA_ARGS__)
|
|
#else
|
|
#define PRINTF(...)
|
|
#endif
|
|
|
|
#ifndef WEBSERVER_CONF_CFS_CONNS
|
|
#define CONNS UIP_CONNS
|
|
#else /* WEBSERVER_CONF_CFS_CONNS */
|
|
#define CONNS WEBSERVER_CONF_CFS_CONNS
|
|
#endif /* WEBSERVER_CONF_CFS_CONNS */
|
|
|
|
#ifndef WEBSERVER_CONF_CFS_URLCONV
|
|
#define URLCONV 0
|
|
#else /* WEBSERVER_CONF_CFS_URLCONV */
|
|
#define URLCONV WEBSERVER_CONF_CFS_URLCONV
|
|
#endif /* WEBSERVER_CONF_CFS_URLCONV */
|
|
|
|
#if URLCONV
|
|
#include "urlconv.h"
|
|
#endif /* URLCONV */
|
|
|
|
static struct httpd_ws_state conns[CONNS];
|
|
|
|
PROCESS(httpd_ws_process, "Web server (WS)");
|
|
|
|
#define ISO_nl 0x0a
|
|
#define ISO_space 0x20
|
|
#define ISO_period 0x2e
|
|
#define ISO_slash 0x2f
|
|
|
|
uint16_t http_connections = 0;
|
|
|
|
static const char http_10[] = " HTTP/1.0\r\n";
|
|
static const char http_content_type[] = "Content-Type:";
|
|
static const char http_content_type_html[] = "text/html";
|
|
static const char http_content_len[] = "Content-Length:";
|
|
static const char http_header_404[] =
|
|
"HTTP/1.0 404 Not found\r\nServer: Contiki\r\nConnection: close\r\n";
|
|
static const char http_header_200[] =
|
|
"HTTP/1.0 200 OK\r\nServer: Contiki\r\nConnection: close\r\n";
|
|
static const char html_not_found[] =
|
|
"<html><body><h1>Page not found</h1></body></html>";
|
|
/*---------------------------------------------------------------------------*/
|
|
/* just set all states to unused */
|
|
static void
|
|
httpd_state_init(void)
|
|
{
|
|
int i;
|
|
|
|
for(i = 0; i < CONNS; i++) {
|
|
conns[i].state = HTTPD_WS_STATE_UNUSED;
|
|
}
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
static struct httpd_ws_state *
|
|
httpd_state_alloc(void)
|
|
{
|
|
int i;
|
|
|
|
for(i = 0; i < CONNS; i++) {
|
|
if(conns[i].state == HTTPD_WS_STATE_UNUSED) {
|
|
conns[i].state = HTTPD_WS_STATE_INPUT;
|
|
return &conns[i];
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
#define httpd_state_free(s) (s->state = HTTPD_WS_STATE_UNUSED)
|
|
/*---------------------------------------------------------------------------*/
|
|
static
|
|
PT_THREAD(send_string(struct httpd_ws_state *s, const char *str, uint16_t len))
|
|
{
|
|
PSOCK_BEGIN(&s->sout);
|
|
|
|
SEND_STRING(&s->sout, str, len);
|
|
|
|
PSOCK_END(&s->sout);
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
static
|
|
PT_THREAD(send_headers(struct httpd_ws_state *s, const char *statushdr))
|
|
{
|
|
PSOCK_BEGIN(&s->sout);
|
|
|
|
SEND_STRING(&s->sout, statushdr, strlen(statushdr));
|
|
s->outbuf_pos = snprintf(s->outbuf, sizeof(s->outbuf),
|
|
"%s %s\r\n\r\n", http_content_type,
|
|
s->content_type == NULL
|
|
? http_content_type_html : s->content_type);
|
|
SEND_STRING(&s->sout, s->outbuf, s->outbuf_pos);
|
|
s->outbuf_pos = 0;
|
|
|
|
PSOCK_END(&s->sout);
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
static
|
|
PT_THREAD(handle_output(struct httpd_ws_state *s))
|
|
{
|
|
PT_BEGIN(&s->outputpt);
|
|
|
|
s->content_type = http_content_type_html;
|
|
s->script = httpd_ws_get_script(s);
|
|
if(s->script == NULL) {
|
|
PT_WAIT_THREAD(&s->outputpt, send_headers(s, http_header_404));
|
|
PT_WAIT_THREAD(&s->outputpt,
|
|
send_string(s, html_not_found, strlen(html_not_found)));
|
|
uip_close();
|
|
/* webserver_log_file(&uip_conn->ripaddr, "404 - not found"); */
|
|
PT_EXIT(&s->outputpt);
|
|
} else {
|
|
if(s->request_type == HTTPD_WS_POST) {
|
|
/* A post has a body that needs to be read */
|
|
s->state = HTTPD_WS_STATE_INPUT;
|
|
PT_WAIT_UNTIL(&s->outputpt, s->state == HTTPD_WS_STATE_OUTPUT);
|
|
}
|
|
PT_WAIT_THREAD(&s->outputpt, send_headers(s, http_header_200));
|
|
PT_WAIT_THREAD(&s->outputpt, s->script(s));
|
|
}
|
|
s->script = NULL;
|
|
PSOCK_CLOSE(&s->sout);
|
|
PT_END(&s->outputpt);
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
static
|
|
PT_THREAD(handle_request(struct httpd_ws_state *s))
|
|
{
|
|
PT_BEGIN(&s->outputpt);
|
|
|
|
/* send the request line */
|
|
PT_WAIT_THREAD(&s->outputpt,
|
|
send_string(s, s->filename, strlen(s->filename)));
|
|
/* send host */
|
|
if(s->outbuf_pos > 0) {
|
|
PT_WAIT_THREAD(&s->outputpt, send_string(s, s->outbuf, s->outbuf_pos));
|
|
}
|
|
|
|
if(s->content_type != NULL) {
|
|
s->outbuf_pos = snprintf(s->outbuf, sizeof(s->outbuf), "%s %s\r\n",
|
|
http_content_type, s->content_type);
|
|
PT_WAIT_THREAD(&s->outputpt, send_string(s, s->outbuf, s->outbuf_pos));
|
|
}
|
|
/* send the extra header(s) */
|
|
if(s->output_extra_headers != NULL) {
|
|
s->response_index = 0;
|
|
while((s->outbuf_pos =
|
|
s->output_extra_headers(s,
|
|
s->outbuf, sizeof(s->outbuf),
|
|
s->response_index)) > 0) {
|
|
PT_WAIT_THREAD(&s->outputpt, send_string(s, s->outbuf, s->outbuf_pos));
|
|
s->response_index++;
|
|
}
|
|
}
|
|
|
|
/* send content length */
|
|
if(s->content_len > 0) {
|
|
s->outbuf_pos = snprintf(s->outbuf, sizeof(s->outbuf), "%s %u\r\n",
|
|
http_content_len, s->content_len);
|
|
}
|
|
/* send header separator */
|
|
if(s->outbuf_pos + 2 < sizeof(s->outbuf)) {
|
|
s->outbuf[s->outbuf_pos++] = '\r';
|
|
s->outbuf[s->outbuf_pos++] = '\n';
|
|
}
|
|
PT_WAIT_THREAD(&s->outputpt, send_string(s, s->outbuf, s->outbuf_pos));
|
|
s->outbuf_pos = 0;
|
|
|
|
if(s->script != NULL) {
|
|
PT_WAIT_THREAD(&s->outputpt, s->script(s));
|
|
}
|
|
s->state = HTTPD_WS_STATE_REQUEST_INPUT;
|
|
|
|
PSOCK_CLOSE(&s->sout);
|
|
PT_END(&s->outputpt);
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
static
|
|
PT_THREAD(handle_input(struct httpd_ws_state *s))
|
|
{
|
|
PSOCK_BEGIN(&s->sin);
|
|
PSOCK_READTO(&s->sin, ISO_space);
|
|
|
|
if(strncmp(s->inputbuf, "GET ", 4) == 0) {
|
|
s->request_type = HTTPD_WS_GET;
|
|
} else if(strncmp(s->inputbuf, "POST ", 5) == 0) {
|
|
s->request_type = HTTPD_WS_POST;
|
|
s->content_len = 0;
|
|
} else if(strncmp(s->inputbuf, "HTTP ", 5) == 0) {
|
|
s->request_type = HTTPD_WS_RESPONSE;
|
|
} else {
|
|
PSOCK_CLOSE_EXIT(&s->sin);
|
|
}
|
|
PSOCK_READTO(&s->sin, ISO_space);
|
|
|
|
/* TODO handle HTTP response */
|
|
|
|
if(s->inputbuf[0] != ISO_slash) {
|
|
PSOCK_CLOSE_EXIT(&s->sin);
|
|
}
|
|
|
|
#if URLCONV
|
|
s->inputbuf[PSOCK_DATALEN(&s->sin) - 1] = 0;
|
|
urlconv_tofilename(s->filename, s->inputbuf, sizeof(s->filename));
|
|
#else /* URLCONV */
|
|
s->inputbuf[PSOCK_DATALEN(&s->sin) - 1] = 0;
|
|
snprintf(s->filename, sizeof(s->filename), "%s", s->inputbuf);
|
|
#endif /* URLCONV */
|
|
|
|
/* webserver_log_file(&uip_conn->ripaddr, s->filename); */
|
|
s->state = HTTPD_WS_STATE_OUTPUT;
|
|
|
|
while(1) {
|
|
PSOCK_READTO(&s->sin, ISO_nl);
|
|
|
|
if(s->request_type == HTTPD_WS_POST &&
|
|
strncmp(s->inputbuf, http_content_len, 15) == 0) {
|
|
s->inputbuf[PSOCK_DATALEN(&s->sin) - 2] = 0;
|
|
s->content_len = atoi(&s->inputbuf[16]);
|
|
}
|
|
|
|
/* should have a header callback here check_header(s) */
|
|
|
|
if(PSOCK_DATALEN(&s->sin) > 2) {
|
|
s->inputbuf[PSOCK_DATALEN(&s->sin) - 2] = 0;
|
|
} else if(s->request_type == HTTPD_WS_POST) {
|
|
PSOCK_READBUF_LEN(&s->sin, s->content_len);
|
|
s->inputbuf[PSOCK_DATALEN(&s->sin)] = 0;
|
|
/* printf("Content: '%s'\nSize:%d\n", s->inputbuf, PSOCK_DATALEN(&s->sin)); */
|
|
s->state = HTTPD_WS_STATE_OUTPUT;
|
|
}
|
|
}
|
|
PSOCK_END(&s->sin);
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
static void
|
|
handle_connection(struct httpd_ws_state *s)
|
|
{
|
|
if(s->state == HTTPD_WS_STATE_REQUEST_OUTPUT) {
|
|
handle_request(s);
|
|
}
|
|
handle_input(s);
|
|
if(s->state == HTTPD_WS_STATE_OUTPUT) {
|
|
handle_output(s);
|
|
}
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
void
|
|
httpd_ws_appcall(void *state)
|
|
{
|
|
struct httpd_ws_state *s = (struct httpd_ws_state *)state;
|
|
|
|
if(uip_closed() || uip_aborted() || uip_timedout()) {
|
|
if(s != NULL) {
|
|
PRINTF("HTTPD-WS: closed/aborted (%d)\n", http_connections);
|
|
http_connections--;
|
|
httpd_state_free(s);
|
|
} else {
|
|
PRINTF("HTTPD-WS: closed/aborted ** NO HTTPD_WS_STATE!!! ** (%d)\n",
|
|
http_connections);
|
|
}
|
|
} else if(uip_connected()) {
|
|
if(s == NULL) {
|
|
s = httpd_state_alloc();
|
|
if(s == NULL) {
|
|
uip_abort();
|
|
PRINTF("HTTPD-WS: aborting - no resource (%d)\n", http_connections);
|
|
/* webserver_log_file(&uip_conn->ripaddr, "reset (no memory block)"); */
|
|
return;
|
|
}
|
|
http_connections++;
|
|
|
|
tcp_markconn(uip_conn, s);
|
|
s->state = HTTPD_WS_STATE_INPUT;
|
|
} else {
|
|
/* this is a request that is to be sent! */
|
|
s->state = HTTPD_WS_STATE_REQUEST_OUTPUT;
|
|
}
|
|
PSOCK_INIT(&s->sin, (uint8_t *)s->inputbuf, sizeof(s->inputbuf) - 1);
|
|
PSOCK_INIT(&s->sout, (uint8_t *)s->inputbuf, sizeof(s->inputbuf) - 1);
|
|
PT_INIT(&s->outputpt);
|
|
timer_set(&s->timer, CLOCK_SECOND * 30);
|
|
handle_connection(s);
|
|
} else if(s != NULL) {
|
|
if(uip_poll()) {
|
|
if(timer_expired(&s->timer)) {
|
|
uip_abort();
|
|
PRINTF("HTTPD-WS: aborting - http timeout (%d)\n", http_connections);
|
|
http_connections--;
|
|
httpd_state_free(s);
|
|
/* webserver_log_file(&uip_conn->ripaddr, "reset (timeout)"); */
|
|
} else {
|
|
PRINTF("HTTPD-WS: uip-poll (%d)\n", http_connections);
|
|
}
|
|
} else {
|
|
/* PRINTF("HTTPD-WS: restart timer %s (%d)\n", s->filename, */
|
|
/* http_connections); */
|
|
timer_restart(&s->timer);
|
|
}
|
|
handle_connection(s);
|
|
} else {
|
|
PRINTF("HTTPD-WS: aborting - no state (%d)\n", http_connections);
|
|
uip_abort();
|
|
}
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
void
|
|
httpd_ws_init(void)
|
|
{
|
|
tcp_listen(UIP_HTONS(80));
|
|
httpd_state_init();
|
|
#if URLCONV
|
|
urlconv_init();
|
|
#endif /* URLCONV */
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
struct httpd_ws_state *
|
|
httpd_ws_request(char request_type, const char *host_ip, const char *host_hdr,
|
|
uint16_t port, const char *file,
|
|
const char *content_type, uint16_t content_len,
|
|
httpd_ws_script_t generator)
|
|
{
|
|
struct httpd_ws_state *s;
|
|
struct uip_conn *conn;
|
|
uip_ipaddr_t *ipaddr;
|
|
uip_ipaddr_t addr;
|
|
char *request_str;
|
|
|
|
/* First check if the host is an IP address. */
|
|
ipaddr = &addr;
|
|
if(uiplib_ipaddrconv(host_ip, &addr) == 0) {
|
|
#if 0 && UIP_UDP
|
|
ipaddr = resolv_lookup(host_ip);
|
|
|
|
if(ipaddr == NULL) {
|
|
return NULL;
|
|
}
|
|
#else /* UIP_UDP */
|
|
return NULL;
|
|
#endif /* UIP_UDP */
|
|
}
|
|
|
|
s = httpd_state_alloc();
|
|
if(s == NULL) {
|
|
/* no memory left... do no request... */
|
|
return NULL;
|
|
}
|
|
http_connections++;
|
|
|
|
switch(request_type) {
|
|
case HTTPD_WS_POST:
|
|
request_str = "POST ";
|
|
break;
|
|
case HTTPD_WS_PUT:
|
|
request_str = "PUT ";
|
|
break;
|
|
default:
|
|
request_str = "GET ";
|
|
break;
|
|
}
|
|
|
|
s->request_type = request_type;
|
|
s->content_len = content_len;
|
|
s->content_type = content_type;
|
|
s->script = generator;
|
|
s->state = HTTPD_WS_STATE_REQUEST_OUTPUT;
|
|
|
|
/* create a request line for a POST - should check size of it!!! */
|
|
/* Assume post for now */
|
|
snprintf(s->filename, sizeof(s->filename), "%s%s%s",
|
|
request_str, file, http_10);
|
|
s->outbuf_pos = snprintf(s->outbuf, sizeof(s->outbuf), "Host:%s\r\n",
|
|
host_hdr != NULL ? host_hdr : host_ip);
|
|
|
|
PROCESS_CONTEXT_BEGIN(&httpd_ws_process);
|
|
conn = tcp_connect(ipaddr, uip_htons(port), s);
|
|
PROCESS_CONTEXT_END(&httpd_ws_process);
|
|
if(conn == NULL) {
|
|
PRINTF("HTTPD-WS: aborting... could not allocate tcp connection (%d)\n",
|
|
http_connections);
|
|
httpd_state_free(s);
|
|
http_connections--;
|
|
return NULL;
|
|
}
|
|
PRINTF("HTTPD-WS: created http connection (%d)\n", http_connections);
|
|
|
|
return s;
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
PROCESS_THREAD(httpd_ws_process, ev, data)
|
|
{
|
|
static struct etimer et;
|
|
int i;
|
|
|
|
PROCESS_BEGIN();
|
|
|
|
httpd_ws_init();
|
|
|
|
PRINTF("Buffer size, input %d, output\n",
|
|
HTTPD_INBUF_SIZE, HTTPD_OUTBUF_SIZE);
|
|
|
|
/* Delay 2-4 seconds */
|
|
etimer_set(&et, CLOCK_SECOND * 10);
|
|
|
|
/* GC any http session that is too long lived - either because other
|
|
end never closed or if any other state cause too long lived http
|
|
sessions */
|
|
while(1) {
|
|
PROCESS_WAIT_EVENT_UNTIL(ev == tcpip_event || etimer_expired(&et));
|
|
if(ev == tcpip_event) {
|
|
httpd_ws_appcall(data);
|
|
} else if(etimer_expired(&et)) {
|
|
PRINTF("HTTPD States: ");
|
|
for(i = 0; i < CONNS; i++) {
|
|
PRINTF("%d ", conns[i].state);
|
|
if(conns[i].state != HTTPD_WS_STATE_UNUSED &&
|
|
timer_expired(&conns[i].timer)) {
|
|
conns[i].state = HTTPD_WS_STATE_UNUSED;
|
|
PRINTF("\n*** RELEASED HTTPD Session\n");
|
|
http_connections--;
|
|
}
|
|
}
|
|
PRINTF("\n");
|
|
etimer_reset(&et);
|
|
}
|
|
}
|
|
|
|
PROCESS_END();
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|