added WebSockets (#331)
This commit is contained in:
parent
f64935eb1d
commit
bce2139f06
16 changed files with 3810 additions and 1363 deletions
|
@ -1,10 +1,11 @@
|
|||
PROGRAM=http_server
|
||||
|
||||
#ESPBAUD=921600
|
||||
|
||||
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
|
||||
|
||||
|
|
|
@ -3,16 +3,15 @@
|
|||
<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 href="websockets">WebSockets</a></li>
|
||||
<li><a href="about">About</a></li>
|
||||
</ul>
|
||||
|
||||
|
|
|
@ -3,24 +3,23 @@
|
|||
<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 href="websockets">WebSockets</a></li>
|
||||
<li><a class="active" href="about">About</a></li>
|
||||
</ul>
|
||||
|
||||
<div class="grid main">
|
||||
<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>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
|
|
@ -3,16 +3,15 @@
|
|||
<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 class="active" href="/">Home</a></li>
|
||||
<li><a href="websockets">WebSockets</a></li>
|
||||
<li><a href="about">About</a></li>
|
||||
</ul>
|
||||
|
||||
|
|
15
examples/http_server/fsdata/fs/js/smoothie_min.js
Normal file
15
examples/http_server/fsdata/fs/js/smoothie_min.js
Normal file
File diff suppressed because one or more lines are too long
130
examples/http_server/fsdata/fs/websockets.html
Normal file
130
examples/http_server/fsdata/fs/websockets.html
Normal 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
|
@ -21,6 +21,28 @@ enum {
|
|||
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[])
|
||||
{
|
||||
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";
|
||||
}
|
||||
|
||||
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) {
|
||||
case SSI_UPTIME:
|
||||
snprintf(pcInsert, iInsertLen, "%d",
|
||||
xTaskGetTickCount() * portTICK_PERIOD_MS / 1000);
|
||||
return "/websockets.html";
|
||||
}
|
||||
|
||||
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;
|
||||
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;
|
||||
case SSI_LED_STATE:
|
||||
snprintf(pcInsert, iInsertLen, (GPIO.OUT & BIT(LED_PIN)) ? "Off" : "On");
|
||||
case 'D': // Disable LED
|
||||
gpio_write(LED_PIN, true);
|
||||
val = 0xDEAD;
|
||||
break;
|
||||
case 'E': // Enable LED
|
||||
gpio_write(LED_PIN, false);
|
||||
val = 0xBEEF;
|
||||
break;
|
||||
default:
|
||||
snprintf(pcInsert, iInsertLen, "N/A");
|
||||
printf("Unknown command\n");
|
||||
val = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
/* Tell the server how many characters to insert */
|
||||
return (strlen(pcInsert));
|
||||
response[1] = (uint8_t) val;
|
||||
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)
|
||||
|
@ -73,6 +158,7 @@ void httpd_task(void *pvParameters)
|
|||
tCGI pCGIs[] = {
|
||||
{"/gpio", (tCGIHandler) gpio_cgi_handler},
|
||||
{"/about", (tCGIHandler) about_cgi_handler},
|
||||
{"/websockets", (tCGIHandler) websocket_cgi_handler},
|
||||
};
|
||||
|
||||
const char *pcConfigSSITags[] = {
|
||||
|
@ -85,6 +171,8 @@ void httpd_task(void *pvParameters)
|
|||
http_set_cgi_handlers(pCGIs, sizeof (pCGIs) / sizeof (pCGIs[0]));
|
||||
http_set_ssi_handler((tSSIHandler) ssi_handler, pcConfigSSITags,
|
||||
sizeof (pcConfigSSITags) / sizeof (pcConfigSSITags[0]));
|
||||
websocket_register_callbacks((tWsOpenHandler) websocket_open_cb,
|
||||
(tWsHandler) websocket_cb);
|
||||
httpd_init();
|
||||
|
||||
for (;;);
|
||||
|
@ -110,5 +198,5 @@ void user_init(void)
|
|||
gpio_write(LED_PIN, true);
|
||||
|
||||
/* initialize tasks */
|
||||
xTaskCreate(&httpd_task, "HTTP Daemon", 1024, NULL, 2, NULL);
|
||||
xTaskCreate(&httpd_task, "HTTP Daemon", 128, NULL, 2, NULL);
|
||||
}
|
||||
|
|
|
@ -268,6 +268,29 @@
|
|||
#define HTTP_ALLOC_HTTP_STATE() (struct http_state *)mem_malloc(sizeof(struct http_state))
|
||||
#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
|
||||
{
|
||||
const char *name;
|
||||
|
@ -341,6 +364,8 @@ struct http_state {
|
|||
struct fs_file *handle;
|
||||
char *file; /* Pointer to first unsent byte in buf. */
|
||||
|
||||
u8_t is_websocket;
|
||||
|
||||
struct tcp_pcb *pcb;
|
||||
#if LWIP_HTTPD_SUPPORT_REQUESTLIST
|
||||
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_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 websocket_send_close(struct tcp_pcb *pcb);
|
||||
|
||||
#if LWIP_HTTPD_FS_ASYNC_READ
|
||||
static void http_continue(void *connection);
|
||||
#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*/
|
||||
|
||||
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_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
|
||||
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
|
||||
|
@ -716,7 +755,11 @@ http_eof(struct tcp_pcb *pcb, struct http_state *hs)
|
|||
hs->keepalive = 1;
|
||||
} else
|
||||
#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);
|
||||
}
|
||||
}
|
||||
|
@ -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? */
|
||||
if (data_len >= MIN_REQ_LEN) {
|
||||
/* 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
|
||||
#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 {
|
||||
|
@ -2272,7 +2379,7 @@ http_poll(void *arg, struct tcp_pcb *pcb)
|
|||
return ERR_OK;
|
||||
} else {
|
||||
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"));
|
||||
http_close_conn(pcb, hs);
|
||||
return ERR_OK;
|
||||
|
@ -2294,6 +2401,94 @@ http_poll(void *arg, struct tcp_pcb *pcb)
|
|||
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.
|
||||
* 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,
|
||||
(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)) {
|
||||
/* error or closed by other side? */
|
||||
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) {
|
||||
parsed = http_parse_request(&p, hs, pcb);
|
||||
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 {
|
||||
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));
|
||||
http_send(pcb, hs);
|
||||
}
|
||||
} else if (parsed == ERR_ARG) {
|
||||
} else if (parsed == ERR_ARG || parsed == ERR_MEM) {
|
||||
/* @todo: close on ERR_USE? */
|
||||
http_close_conn(pcb, hs);
|
||||
}
|
||||
|
|
|
@ -39,6 +39,7 @@
|
|||
#include "lwip/opt.h"
|
||||
#include "lwip/err.h"
|
||||
#include "lwip/pbuf.h"
|
||||
#include "lwip/tcp.h"
|
||||
|
||||
|
||||
/** Set this to 1 to support CGI */
|
||||
|
@ -56,7 +57,6 @@
|
|||
#define LWIP_HTTPD_SUPPORT_POST 0
|
||||
#endif
|
||||
|
||||
|
||||
#if LWIP_HTTPD_CGI
|
||||
|
||||
/*
|
||||
|
@ -231,6 +231,33 @@ void httpd_post_data_recved(void *connection, u16_t recved_len);
|
|||
|
||||
#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);
|
||||
|
||||
#endif /* __HTTPD_H__ */
|
||||
|
|
|
@ -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.
|
||||
See examples/http_server.
|
||||
|
|
80
extras/httpd/strcasestr.c
Normal file
80
extras/httpd/strcasestr.c
Normal 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);
|
||||
}
|
9
extras/httpd/strcasestr.h
Normal file
9
extras/httpd/strcasestr.h
Normal 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 */
|
Loading…
Reference in a new issue