added WebSockets (#331)

This commit is contained in:
lujji 2017-02-02 06:17:53 +00:00 committed by Ruslan V. Uss
parent f64935eb1d
commit bce2139f06
16 changed files with 3810 additions and 1363 deletions

View file

@ -1,10 +1,11 @@
PROGRAM=http_server PROGRAM=http_server
#ESPBAUD=921600
EXTRA_CFLAGS=-DLWIP_HTTPD_CGI=1 -DLWIP_HTTPD_SSI=1 -I./fsdata EXTRA_CFLAGS=-DLWIP_HTTPD_CGI=1 -DLWIP_HTTPD_SSI=1 -I./fsdata
EXTRA_COMPONENTS=extras/httpd #Enable debugging
#EXTRA_CFLAGS+=-DLWIP_DEBUG=1 -DHTTPD_DEBUG=LWIP_DBG_ON
EXTRA_COMPONENTS=extras/mbedtls extras/httpd
include ../../common.mk include ../../common.mk

View file

@ -3,16 +3,15 @@
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no"> <meta name="viewport" content="width=device-width, user-scalable=no">
<link rel="stylesheet" type="text/css" href="css/siimple.min.css"> <link rel="stylesheet" type="text/css" href="css/siimple.min.css">
<link rel="stylesheet" type="text/css" href="css/style.css"> <link rel="stylesheet" type="text/css" href="css/style.css">
<link rel="shortcut icon" href="img/favicon.png"> <link rel="shortcut icon" href="img/favicon.png">
<title>HTTP Server</title> <title>HTTP Server</title>
</head> </head>
<body> <body>
<ul class="navbar"> <ul class="navbar">
<li><a href="/">Home</a></li> <li><a href="/">Home</a></li>
<li><a href="websockets">WebSockets</a></li>
<li><a href="about">About</a></li> <li><a href="about">About</a></li>
</ul> </ul>

View file

@ -3,24 +3,23 @@
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no"> <meta name="viewport" content="width=device-width, user-scalable=no">
<link rel="stylesheet" type="text/css" href="css/siimple.min.css"> <link rel="stylesheet" type="text/css" href="css/siimple.min.css">
<link rel="stylesheet" type="text/css" href="css/style.css"> <link rel="stylesheet" type="text/css" href="css/style.css">
<link rel="shortcut icon" href="img/favicon.png"> <link rel="shortcut icon" href="img/favicon.png">
<title>HTTP Server</title> <title>HTTP Server</title>
</head> </head>
<body> <body>
<ul class="navbar"> <ul class="navbar">
<li><a href="/">Home</a></li> <li><a href="/">Home</a></li>
<li><a href="websockets">WebSockets</a></li>
<li><a class="active" href="about">About</a></li> <li><a class="active" href="about">About</a></li>
</ul> </ul>
<div class="grid main"> <div class="grid main">
<h1>About</h1> <h1>About</h1>
<p>This server is built on httpd from LwIP.</p> <p>This server is based on httpd from LwIP.</p>
<p>To enable debugging compile with flags -DLWIP_DEBUG=1 -DHTTPD_DEBUG=LWIP_DBG_ON.</p>
<p>For more info see <a href="http://www.nongnu.org/lwip/2_0_0/group__httpd.html">HTTP Server documentation</a>.</p> <p>For more info see <a href="http://www.nongnu.org/lwip/2_0_0/group__httpd.html">HTTP Server documentation</a>.</p>
</div> </div>
</body> </body>
</html> </html>

View file

@ -3,16 +3,15 @@
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no"> <meta name="viewport" content="width=device-width, user-scalable=no">
<link rel="stylesheet" type="text/css" href="css/siimple.min.css"> <link rel="stylesheet" type="text/css" href="css/siimple.min.css">
<link rel="stylesheet" type="text/css" href="css/style.css"> <link rel="stylesheet" type="text/css" href="css/style.css">
<link rel="shortcut icon" href="img/favicon.png"> <link rel="shortcut icon" href="img/favicon.png">
<title>HTTP Server</title> <title>HTTP Server</title>
</head> </head>
<body> <body>
<ul class="navbar"> <ul class="navbar">
<li><a class="active" href="/">Home</a></li> <li><a class="active" href="/">Home</a></li>
<li><a href="websockets">WebSockets</a></li>
<li><a href="about">About</a></li> <li><a href="about">About</a></li>
</ul> </ul>

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,130 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no">
<link rel="stylesheet" type="text/css" href="css/siimple.min.css">
<link rel="stylesheet" type="text/css" href="css/style.css">
<link rel="shortcut icon" href="img/favicon.png">
<title>HTTP Server</title>
</head>
<body>
<ul class="navbar">
<li><a href="/">Home</a></li>
<li><a class="active" href="websockets">WebSockets</a></li>
<li><a href="about">About</a></li>
</ul>
<div class="grid main">
<h1>WebSockets Demo</h1>
<div id="status_box" class="alert alert-info">Loading..</div>
<p>This page is similar to the home page but uses WebSockets for real-time updates.</p>
<div class="cover" align="center">
<canvas id="chartCanvas" width="512" height="100"></canvas>
<p/>
<p>LED Control</p>
<div class="onoffswitch">
<input type="checkbox" name="onoffswitch" class="onoffswitch-checkbox" id="led-switch" onclick="gpio()">
<label class="onoffswitch-label" for="led-switch">
<span class="onoffswitch-inner"></span>
<span class="onoffswitch-switch"></span>
</label>
</div>
</div>
<h1>Server Status</h1>
<table class="table table-striped">
<tr>
<td><b>Uptime:</b></td>
<td id="uptime"></td>
</tr>
<tr>
<td><b>Free heap:</b></td>
<td id="heap"></td>
</tr>
<tr>
<td><b>LED state:</b></td>
<td id="led"></td>
</tr>
</table>
<h1>How it works</h1>
<p>This demo uses 2 WebScokets. Status parameters are streamed by the server in JSON format every 2 seconds.
A <code>websocket_task</code> is created each time a specific URI is requested.</p>
<p>ADC values are being continuously polled by the client (i.e. your browser).
Each time a WebSocket frame is received on the server side, <code>websocket_cb</code> function is being called.</p>
</div>
<script type="text/javascript" src="js/smoothie_min.js"></script>
<script>
var ws;
var retries;
var series = new TimeSeries();
window.onload = function() {
wsOpen();
startPolling();
}
function setMsg(cls, text) {
sbox = document.getElementById('status_box');
sbox.className = "alert alert-" + cls;
sbox.innerHTML = text;
console.log(text);
}
function startPolling() {
var chart = new SmoothieChart({millisPerPixel:11,grid:{fillStyle:'#ffffff',strokeStyle:'#ffffff',borderVisible:false},
labels:{fillStyle:'#000000'},maxValue:1024,minValue:0});
chart.addTimeSeries(series, {lineWidth:2,strokeStyle:'#03a9f4',fillStyle:'#f1f5fa'});
chart.streamTo(document.getElementById("chartCanvas"), 500);
setInterval(function() { wsWrite('A'); }, 500);
}
function onMessage(evt) {
retries = 0;
var dv = new DataView(evt.data);
var val = dv.getUint16(0);
if (val == 0xBEEF || val == 0xDEAD)
console.log("LED switched");
else
series.append(new Date().getTime(), val);
}
function wsOpen() {
if (ws === undefined || ws.readyState != 0) {
if (retries)
setMsg("error", "WebSocket timeout, retrying..");
else
setMsg("info", "Opening WebSocket..");
ws = new WebSocket("ws://" + location.host);
ws.binaryType = 'arraybuffer';
ws.onopen = function(evt) { retries = 0; setMsg("done", "WebSocket is open."); };
ws.onerror = function(evt) { setMsg("error", "WebSocket error!"); };
ws.onmessage = function(evt) { onMessage(evt); };
wsOpenStream();
retries = 0;
}
}
function wsOpenStream() {
var uri = "/stream"
var ws = new WebSocket("ws://" + location.host + uri);
ws.onmessage = function(evt) {
console.log(evt.data);
var stats = JSON.parse(evt.data);
console.log(stats);
document.getElementById('uptime').innerHTML = stats.uptime + ' seconds';
document.getElementById('heap').innerHTML = stats.heap + ' bytes';
document.getElementById('led').innerHTML = (stats.led == 1) ? 'On' : 'Off';
};
}
function wsWrite(data) {
if (ws.readyState == 3 || retries++ > 5)
wsOpen();
else if (ws.readyState == 1)
ws.send(data);
}
function gpio() {
if (document.getElementById('led-switch').checked)
wsWrite('E');
else
wsWrite('D');
}
</script>
</body>
</html>

File diff suppressed because it is too large Load diff

View file

@ -21,6 +21,28 @@ enum {
SSI_LED_STATE SSI_LED_STATE
}; };
int32_t ssi_handler(int32_t iIndex, char *pcInsert, int32_t iInsertLen)
{
switch (iIndex) {
case SSI_UPTIME:
snprintf(pcInsert, iInsertLen, "%d",
xTaskGetTickCount() * portTICK_PERIOD_MS / 1000);
break;
case SSI_FREE_HEAP:
snprintf(pcInsert, iInsertLen, "%d", (int) xPortGetFreeHeapSize());
break;
case SSI_LED_STATE:
snprintf(pcInsert, iInsertLen, (GPIO.OUT & BIT(LED_PIN)) ? "Off" : "On");
break;
default:
snprintf(pcInsert, iInsertLen, "N/A");
break;
}
/* Tell the server how many characters to insert */
return (strlen(pcInsert));
}
char *gpio_cgi_handler(int iIndex, int iNumParams, char *pcParam[], char *pcValue[]) char *gpio_cgi_handler(int iIndex, int iNumParams, char *pcParam[], char *pcValue[])
{ {
for (int i = 0; i < iNumParams; i++) { for (int i = 0; i < iNumParams; i++) {
@ -46,26 +68,89 @@ char *about_cgi_handler(int iIndex, int iNumParams, char *pcParam[], char *pcVal
return "/about.html"; return "/about.html";
} }
int32_t ssi_handler(int32_t iIndex, char *pcInsert, int32_t iInsertLen) char *websocket_cgi_handler(int iIndex, int iNumParams, char *pcParam[], char *pcValue[])
{ {
switch (iIndex) { return "/websockets.html";
case SSI_UPTIME: }
snprintf(pcInsert, iInsertLen, "%d",
xTaskGetTickCount() * portTICK_PERIOD_MS / 1000); void websocket_task(void *pvParameter)
{
struct tcp_pcb *pcb = (struct tcp_pcb *) pvParameter;
for (;;) {
if (pcb == NULL || pcb->state != ESTABLISHED) {
printf("Connection closed, deleting task\n");
break; break;
case SSI_FREE_HEAP: }
snprintf(pcInsert, iInsertLen, "%d", (int) xPortGetFreeHeapSize());
int uptime = xTaskGetTickCount() * portTICK_PERIOD_MS / 1000;
int heap = (int) xPortGetFreeHeapSize();
int led = !gpio_read(LED_PIN);
/* Generate response in JSON format */
char response[64];
int len = snprintf(response, sizeof (response),
"{\"uptime\" : \"%d\","
" \"heap\" : \"%d\","
" \"led\" : \"%d\"}", uptime, heap, led);
if (len < sizeof (response))
websocket_write(pcb, (unsigned char *) response, len, WS_TEXT_MODE);
vTaskDelay(2000 / portTICK_PERIOD_MS);
}
vTaskDelete(NULL);
}
/**
* This function is called when websocket frame is received.
*
* Note: this function is executed on TCP thread and should return as soon
* as possible.
*/
void websocket_cb(struct tcp_pcb *pcb, uint8_t *data, u16_t data_len, uint8_t mode)
{
printf("[websocket_callback]:\n%.*s\n", (int) data_len, (char*) data);
uint8_t response[2];
uint16_t val;
switch (data[0]) {
case 'A': // ADC
/* This should be done on a separate thread in 'real' applications */
val = sdk_system_adc_read();
break; break;
case SSI_LED_STATE: case 'D': // Disable LED
snprintf(pcInsert, iInsertLen, (GPIO.OUT & BIT(LED_PIN)) ? "Off" : "On"); gpio_write(LED_PIN, true);
val = 0xDEAD;
break;
case 'E': // Enable LED
gpio_write(LED_PIN, false);
val = 0xBEEF;
break; break;
default: default:
snprintf(pcInsert, iInsertLen, "N/A"); printf("Unknown command\n");
val = 0;
break; break;
} }
/* Tell the server how many characters to insert */ response[1] = (uint8_t) val;
return (strlen(pcInsert)); response[0] = val >> 8;
websocket_write(pcb, response, 2, WS_BIN_MODE);
}
/**
* This function is called when new websocket is open and
* creates a new websocket_task if requested URI equals '/stream'.
*/
void websocket_open_cb(struct tcp_pcb *pcb, const char *uri)
{
printf("WS URI: %s\n", uri);
if (!strcmp(uri, "/stream")) {
printf("request for streaming\n");
xTaskCreate(&websocket_task, "websocket_task", 256, (void *) pcb, 2, NULL);
}
} }
void httpd_task(void *pvParameters) void httpd_task(void *pvParameters)
@ -73,6 +158,7 @@ void httpd_task(void *pvParameters)
tCGI pCGIs[] = { tCGI pCGIs[] = {
{"/gpio", (tCGIHandler) gpio_cgi_handler}, {"/gpio", (tCGIHandler) gpio_cgi_handler},
{"/about", (tCGIHandler) about_cgi_handler}, {"/about", (tCGIHandler) about_cgi_handler},
{"/websockets", (tCGIHandler) websocket_cgi_handler},
}; };
const char *pcConfigSSITags[] = { const char *pcConfigSSITags[] = {
@ -85,6 +171,8 @@ void httpd_task(void *pvParameters)
http_set_cgi_handlers(pCGIs, sizeof (pCGIs) / sizeof (pCGIs[0])); http_set_cgi_handlers(pCGIs, sizeof (pCGIs) / sizeof (pCGIs[0]));
http_set_ssi_handler((tSSIHandler) ssi_handler, pcConfigSSITags, http_set_ssi_handler((tSSIHandler) ssi_handler, pcConfigSSITags,
sizeof (pcConfigSSITags) / sizeof (pcConfigSSITags[0])); sizeof (pcConfigSSITags) / sizeof (pcConfigSSITags[0]));
websocket_register_callbacks((tWsOpenHandler) websocket_open_cb,
(tWsHandler) websocket_cb);
httpd_init(); httpd_init();
for (;;); for (;;);
@ -110,5 +198,5 @@ void user_init(void)
gpio_write(LED_PIN, true); gpio_write(LED_PIN, true);
/* initialize tasks */ /* initialize tasks */
xTaskCreate(&httpd_task, "HTTP Daemon", 1024, NULL, 2, NULL); xTaskCreate(&httpd_task, "HTTP Daemon", 128, NULL, 2, NULL);
} }

View file

@ -268,6 +268,29 @@
#define HTTP_ALLOC_HTTP_STATE() (struct http_state *)mem_malloc(sizeof(struct http_state)) #define HTTP_ALLOC_HTTP_STATE() (struct http_state *)mem_malloc(sizeof(struct http_state))
#endif /* HTTPD_USE_MEM_POOL */ #endif /* HTTPD_USE_MEM_POOL */
#include <mbedtls/sha1.h>
#include <mbedtls/base64.h>
#include "strcasestr.h"
static const char WS_HEADER[] = "Upgrade: websocket\r\n";
static const char WS_GUID[] = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
static const char WS_RSP[] = "HTTP/1.1 101 Switching Protocols\r\n" \
"Upgrade: websocket\r\n" \
"Connection: Upgrade\r\n" \
"Sec-WebSocket-Accept: ";
/* Response buffer length (30 = base64 encoded key max length) */
#define WS_BUF_LEN (sizeof(WS_RSP) + sizeof(CRLF CRLF) + 30 - 2)
/* WebSocket timeout: X*(HTTPD_POLL_INTERVAL), default is 10*4*500ms = 20s */
#ifndef WS_TIMEOUT
#define WS_TIMEOUT 10
#endif
/* Callback functions */
static tWsHandler websocket_cb = NULL;
static tWsOpenHandler websocket_open_cb = NULL;
typedef struct typedef struct
{ {
const char *name; const char *name;
@ -341,6 +364,8 @@ struct http_state {
struct fs_file *handle; struct fs_file *handle;
char *file; /* Pointer to first unsent byte in buf. */ char *file; /* Pointer to first unsent byte in buf. */
u8_t is_websocket;
struct tcp_pcb *pcb; struct tcp_pcb *pcb;
#if LWIP_HTTPD_SUPPORT_REQUESTLIST #if LWIP_HTTPD_SUPPORT_REQUESTLIST
struct pbuf *req; struct pbuf *req;
@ -386,6 +411,9 @@ static err_t http_close_or_abort_conn(struct tcp_pcb *pcb, struct http_state *hs
static err_t http_find_file(struct http_state *hs, const char *uri, int is_09); static err_t http_find_file(struct http_state *hs, const char *uri, int is_09);
static err_t http_init_file(struct http_state *hs, struct fs_file *file, int is_09, const char *uri, u8_t tag_check); static err_t http_init_file(struct http_state *hs, struct fs_file *file, int is_09, const char *uri, u8_t tag_check);
static err_t http_poll(void *arg, struct tcp_pcb *pcb); static err_t http_poll(void *arg, struct tcp_pcb *pcb);
static err_t websocket_send_close(struct tcp_pcb *pcb);
#if LWIP_HTTPD_FS_ASYNC_READ #if LWIP_HTTPD_FS_ASYNC_READ
static void http_continue(void *connection); static void http_continue(void *connection);
#endif /* LWIP_HTTPD_FS_ASYNC_READ */ #endif /* LWIP_HTTPD_FS_ASYNC_READ */
@ -666,6 +694,17 @@ http_close_or_abort_conn(struct tcp_pcb *pcb, struct http_state *hs, u8_t abort_
} }
#endif /* LWIP_HTTPD_SUPPORT_POST*/ #endif /* LWIP_HTTPD_SUPPORT_POST*/
if (hs != NULL) {
if (hs->is_websocket)
websocket_send_close(pcb);
if (hs->req != NULL) {
/* this should not happen */
LWIP_DEBUGF(HTTPD_DEBUG, ("Freeing buffer (malformed request?)\n"));
pbuf_free(hs->req);
hs->req = NULL;
}
}
tcp_arg(pcb, NULL); tcp_arg(pcb, NULL);
tcp_recv(pcb, NULL); tcp_recv(pcb, NULL);
@ -699,7 +738,7 @@ http_close_or_abort_conn(struct tcp_pcb *pcb, struct http_state *hs, u8_t abort_
static err_t static err_t
http_close_conn(struct tcp_pcb *pcb, struct http_state *hs) http_close_conn(struct tcp_pcb *pcb, struct http_state *hs)
{ {
return http_close_or_abort_conn(pcb, hs, 0); return http_close_or_abort_conn(pcb, hs, 0);
} }
/** End of file: either close the connection (Connection: close) or /** End of file: either close the connection (Connection: close) or
@ -716,7 +755,11 @@ http_eof(struct tcp_pcb *pcb, struct http_state *hs)
hs->keepalive = 1; hs->keepalive = 1;
} else } else
#endif /* LWIP_HTTPD_SUPPORT_11_KEEPALIVE */ #endif /* LWIP_HTTPD_SUPPORT_11_KEEPALIVE */
{ if (hs->is_websocket) {
http_state_eof(hs);
http_state_init(hs);
hs->is_websocket = 1;
} else {
http_close_conn(pcb, hs); http_close_conn(pcb, hs);
} }
} }
@ -1915,6 +1958,60 @@ http_parse_request(struct pbuf **inp, struct http_state *hs, struct tcp_pcb *pcb
} }
} }
/* Parse WebSocket request */
hs->is_websocket = 0;
unsigned char *retval = NULL;
if (strncasestr(data, WS_HEADER, data_len)) {
LWIP_DEBUGF(HTTPD_DEBUG, ("WebSocket opening handshake\n"));
char *key_start = strncasestr(data, "Sec-WebSocket-Key: ", data_len);
if (key_start) {
key_start += 19;
char *key_end = strncasestr(key_start, "\r\n", data_len);
if (key_end) {
char key[64];
int len = sizeof (char) * (key_end - key_start);
if ((len + sizeof (WS_GUID) < sizeof (key)) && (len > 0)) {
/* Allocate response buffer */
retval = mem_malloc(WS_BUF_LEN);
if (retval == NULL) {
LWIP_DEBUGF(HTTPD_DEBUG, ("Out of memory\n"));
return ERR_MEM;
}
unsigned char *retval_ptr;
retval_ptr = memcpy(retval, WS_RSP, sizeof(WS_RSP));
retval_ptr += sizeof(WS_RSP) - 1;
/* Concatenate key */
memcpy(key, key_start, len);
strlcpy(&key[len], WS_GUID, sizeof(key));
LWIP_DEBUGF(HTTPD_DEBUG, ("Resulting key: %s\n", key));
/* Get SHA1 */
int key_len = sizeof(WS_GUID) - 1 + len;
unsigned char sha1sum[20];
mbedtls_sha1((unsigned char *) key, key_len, sha1sum);
/* Base64 encode */
unsigned int olen;
mbedtls_base64_encode(NULL, 0, &olen, sha1sum, 20); //get length
int ok = mbedtls_base64_encode(retval_ptr, WS_BUF_LEN, &olen, sha1sum, 20);
if (ok == 0) {
memcpy(&retval_ptr[olen], CRLF CRLF, sizeof(CRLF CRLF));
hs->is_websocket = 1;
LWIP_DEBUGF(HTTPD_DEBUG, ("Base64 encoded: %s\n", retval_ptr));
}
} else {
LWIP_DEBUGF(HTTPD_DEBUG, ("Key overflow"));
return ERR_MEM;
}
}
} else {
LWIP_DEBUGF(HTTPD_DEBUG, ("error: malformed packet\n"));
return ERR_ARG;
}
}
/* received enough data for minimal request? */ /* received enough data for minimal request? */
if (data_len >= MIN_REQ_LEN) { if (data_len >= MIN_REQ_LEN) {
/* wait for CRLF before parsing anything */ /* wait for CRLF before parsing anything */
@ -2000,7 +2097,17 @@ http_parse_request(struct pbuf **inp, struct http_state *hs, struct tcp_pcb *pcb
} else } else
#endif /* LWIP_HTTPD_SUPPORT_POST */ #endif /* LWIP_HTTPD_SUPPORT_POST */
{ {
return http_find_file(hs, uri, is_09); if (hs->is_websocket && retval != NULL) {
LWIP_DEBUGF(HTTPD_DEBUG, ("Sending:\n%s\n", retval));
u16_t len = strlen((char *) retval);
http_write(pcb, retval, &len, 0);
mem_free(retval);
if(websocket_open_cb)
websocket_open_cb(pcb, uri);
return ERR_OK; // We handled this
} else {
return http_find_file(hs, uri, is_09);
}
} }
} }
} else { } else {
@ -2272,7 +2379,7 @@ http_poll(void *arg, struct tcp_pcb *pcb)
return ERR_OK; return ERR_OK;
} else { } else {
hs->retries++; hs->retries++;
if (hs->retries == HTTPD_MAX_RETRIES) { if (hs->retries == ((hs->is_websocket) ? WS_TIMEOUT : HTTPD_MAX_RETRIES)) {
LWIP_DEBUGF(HTTPD_DEBUG, ("http_poll: too many retries, close\n")); LWIP_DEBUGF(HTTPD_DEBUG, ("http_poll: too many retries, close\n"));
http_close_conn(pcb, hs); http_close_conn(pcb, hs);
return ERR_OK; return ERR_OK;
@ -2294,6 +2401,94 @@ http_poll(void *arg, struct tcp_pcb *pcb)
return ERR_OK; return ERR_OK;
} }
void
websocket_register_callbacks(tWsOpenHandler ws_open_cb, tWsHandler ws_cb)
{
websocket_open_cb = ws_open_cb;
websocket_cb = ws_cb;
}
err_t
websocket_write(struct tcp_pcb *pcb, const uint8_t *data, uint16_t len, uint8_t mode)
{
if (len > 125)
return ERR_BUF;
unsigned char buf[len + 2];
buf[0] = 0x80 | mode;
buf[1] = len;
memcpy(&buf[2], data, len);
len += 2;
LWIP_DEBUGF(HTTPD_DEBUG, ("[wsoc] sending packet\n"));
return http_write(pcb, buf, &len, TCP_WRITE_FLAG_COPY);
}
/**
* Send status code 1000 (normal closure).
*/
static err_t
websocket_send_close(struct tcp_pcb *pcb)
{
const char buf[] = {0x88, 0x02, 0x03, 0xe8};
u16_t len = sizeof (buf);
LWIP_DEBUGF(HTTPD_DEBUG, ("[wsoc] closing connection\n"));
return tcp_write(pcb, buf, len, TCP_WRITE_FLAG_COPY);
}
/**
* Parse websocket frame.
*
* @return ERR_OK: frame parsed
* ERR_CLSD: close request from client
* ERR_VAL: invalid frame.
*/
static err_t
websocket_parse(struct tcp_pcb *pcb, struct pbuf *p)
{
unsigned char *data;
data = (unsigned char*) p->payload;
u16_t data_len = p->len;
if (data != NULL && data_len > 1) {
LWIP_DEBUGF(HTTPD_DEBUG, ("[wsoc] frame received\n"));
u8_t opcode = data[0] & 0x0F;
switch (opcode) {
case WS_TEXT_MODE:
case WS_BIN_MODE:
LWIP_DEBUGF(HTTPD_DEBUG, ("Opcode: 0x%hX, frame length: %d\n", opcode, data_len));
if (data_len > 6) {
u8_t len = data[1] & 0x7F;
if (len > data_len || len > 125) {
LWIP_DEBUGF(HTTPD_DEBUG, ("Error: large frames not supported\n"));
return ERR_VAL;
} else {
if (data_len - 6 != len)
LWIP_DEBUGF(HTTPD_DEBUG, ("Multiple frames received\n"));
data_len = len;
}
/* unmask */
for (int i = 0; i < data_len; i++)
data[i + 6] = (data[i + 6] ^ data[2 + i % 4]);
/* user callback */
if (websocket_cb)
websocket_cb(pcb, &data[6], data_len, opcode);
}
break;
case 0x08: // close
LWIP_DEBUGF(HTTPD_DEBUG, ("Close request\n"));
return ERR_CLSD;
break;
default:
LWIP_DEBUGF(HTTPD_DEBUG, ("Unsupported opcode 0x%hX\n", opcode));
break;
}
return ERR_OK;
}
return ERR_VAL;
}
/** /**
* Data has been received on this pcb. * Data has been received on this pcb.
* For HTTP 1.0, this should normally only happen once (if the request fits in one packet). * For HTTP 1.0, this should normally only happen once (if the request fits in one packet).
@ -2306,6 +2501,29 @@ http_recv(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err)
LWIP_DEBUGF(HTTPD_DEBUG | LWIP_DBG_TRACE, ("http_recv: pcb=%p pbuf=%p err=%s\n", (void*)pcb, LWIP_DEBUGF(HTTPD_DEBUG | LWIP_DBG_TRACE, ("http_recv: pcb=%p pbuf=%p err=%s\n", (void*)pcb,
(void*)p, lwip_strerr(err))); (void*)p, lwip_strerr(err)));
if (hs != NULL && hs->is_websocket) {
if (p == NULL) {
LWIP_DEBUGF(HTTPD_DEBUG, ("http_recv: buffer error\n"));
http_close_or_abort_conn(pcb, hs, 0);
return ERR_BUF;
}
tcp_recved(pcb, p->tot_len);
err_t err = websocket_parse(pcb, p);
if (p != NULL) {
/* otherwise tcp buffer hogs */
LWIP_DEBUGF(HTTPD_DEBUG, ("[wsoc] freeing buffer\n"));
pbuf_free(p);
}
if (err == ERR_CLSD) {
http_close_conn(pcb, hs);
}
return ERR_OK;
}
if ((err != ERR_OK) || (p == NULL) || (hs == NULL)) { if ((err != ERR_OK) || (p == NULL) || (hs == NULL)) {
/* error or closed by other side? */ /* error or closed by other side? */
if (p != NULL) { if (p != NULL) {
@ -2349,7 +2567,8 @@ http_recv(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err)
if (hs->handle == NULL) { if (hs->handle == NULL) {
parsed = http_parse_request(&p, hs, pcb); parsed = http_parse_request(&p, hs, pcb);
LWIP_ASSERT("http_parse_request: unexpected return value", parsed == ERR_OK LWIP_ASSERT("http_parse_request: unexpected return value", parsed == ERR_OK
|| parsed == ERR_INPROGRESS ||parsed == ERR_ARG || parsed == ERR_USE); || parsed == ERR_INPROGRESS ||parsed == ERR_ARG
|| parsed == ERR_USE || parsed == ERR_MEM);
} else { } else {
LWIP_DEBUGF(HTTPD_DEBUG, ("http_recv: already sending data\n")); LWIP_DEBUGF(HTTPD_DEBUG, ("http_recv: already sending data\n"));
} }
@ -2375,7 +2594,7 @@ http_recv(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err)
LWIP_DEBUGF(HTTPD_DEBUG | LWIP_DBG_TRACE, ("http_recv: data %p len %"S32_F"\n", hs->file, hs->left)); LWIP_DEBUGF(HTTPD_DEBUG | LWIP_DBG_TRACE, ("http_recv: data %p len %"S32_F"\n", hs->file, hs->left));
http_send(pcb, hs); http_send(pcb, hs);
} }
} else if (parsed == ERR_ARG) { } else if (parsed == ERR_ARG || parsed == ERR_MEM) {
/* @todo: close on ERR_USE? */ /* @todo: close on ERR_USE? */
http_close_conn(pcb, hs); http_close_conn(pcb, hs);
} }

View file

@ -39,6 +39,7 @@
#include "lwip/opt.h" #include "lwip/opt.h"
#include "lwip/err.h" #include "lwip/err.h"
#include "lwip/pbuf.h" #include "lwip/pbuf.h"
#include "lwip/tcp.h"
/** Set this to 1 to support CGI */ /** Set this to 1 to support CGI */
@ -56,7 +57,6 @@
#define LWIP_HTTPD_SUPPORT_POST 0 #define LWIP_HTTPD_SUPPORT_POST 0
#endif #endif
#if LWIP_HTTPD_CGI #if LWIP_HTTPD_CGI
/* /*
@ -231,6 +231,33 @@ void httpd_post_data_recved(void *connection, u16_t recved_len);
#endif /* LWIP_HTTPD_SUPPORT_POST */ #endif /* LWIP_HTTPD_SUPPORT_POST */
enum {
WS_TEXT_MODE = 0x01,
WS_BIN_MODE = 0x02,
} WS_MODE;
typedef void (*tWsHandler)(struct tcp_pcb *pcb, uint8_t *data, u16_t data_len, uint8_t mode);
typedef void (*tWsOpenHandler)(struct tcp_pcb *pcb, const char *uri);
/**
* Write data into a websocket.
*
* @param pcb tcp_pcb to send.
* @param data data to send.
* @param len data length (should not exceed 125).
* @param mode WS_TEXT_MODE or WS_BIN_MODE.
* @return ERR_OK if write succeeded.
*/
err_t websocket_write(struct tcp_pcb *pcb, const uint8_t *data, uint16_t len, uint8_t mode);
/**
* Register websocket callback functions. Use NULL if callback is not needed.
*
* @param ws_open_cb called when new websocket is opened.
* @param ws_cb called when data is received from client.
*/
void websocket_register_callbacks(tWsOpenHandler ws_open_cb, tWsHandler ws_cb);
void httpd_init(void); void httpd_init(void);
#endif /* __HTTPD_H__ */ #endif /* __HTTPD_H__ */

View file

@ -1,2 +1,4 @@
Maintained by lujji (https://github.com/lujji/esp-httpd).
Note: this module expects your project to provide "fsdata.c" created with "makefsdata" utility. Note: this module expects your project to provide "fsdata.c" created with "makefsdata" utility.
See examples/http_server. See examples/http_server.

80
extras/httpd/strcasestr.c Normal file
View file

@ -0,0 +1,80 @@
/*-
* Copyright (c) 2001 Mike Barcroft <mike@FreeBSD.org>
* Copyright (c) 1990, 1993
* The Regents of the University of California. All rights reserved.
*
* This code is derived from software contributed to Berkeley by
* Chris Torek.
*
* 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 University 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 REGENTS 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 REGENTS 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.
*/
#include "strcasestr.h"
#include <ctype.h>
#include <string.h>
char *
strcasestr(s, find)
const char *s, *find;
{
char c, sc;
size_t len;
if ((c = *find++) != 0) {
c = tolower((unsigned char) c);
len = strlen(find);
do {
do {
if ((sc = *s++) == 0)
return (NULL);
} while ((char) tolower((unsigned char) sc) != c);
} while (strncasecmp(s, find, len) != 0);
s--;
}
return ((char *) s);
}
char *
strncasestr(s, find, slen)
const char *s;
const char *find;
size_t slen;
{
char c, sc;
size_t len;
if ((c = *find++) != '\0') {
len = strlen(find);
do {
do {
if (slen-- < 1 || (sc = *s++) == '\0')
return (NULL);
} while (sc != c);
if (len > slen)
return (NULL);
} while (strncasecmp(s, find, len) != 0);
s--;
}
return ((char *) s);
}

View file

@ -0,0 +1,9 @@
#ifndef STRCASESTR_H
#define STRCASESTR_H
#include <sys/types.h>
char *strcasestr(const char *s, const char *find);
char *strncasestr(const char *s, const char * find, size_t slen);
#endif /* STRCASESTR_H */