http_server: websocket server to support async send

This commit is contained in:
David Cermak
2020-03-11 10:03:12 +01:00
committed by bot
parent d7b3a051f0
commit 1b842ce1a8
9 changed files with 111 additions and 29 deletions
@@ -22,30 +22,79 @@
/* A simple example that demonstrates using websocket echo server
*/
static const char *TAG = "ws_echo_server";
/*
* Structure holding server handle
* and internal socket fd in order
* to use out of request send
*/
struct async_resp_arg {
httpd_handle_t hd;
int fd;
};
static esp_err_t ws_ping_handler(httpd_req_t *req)
/*
* async send function, which we put into the httpd work queue
*/
static void ws_async_send(void *arg)
{
static const char * data = "Async data";
struct async_resp_arg *resp_arg = arg;
httpd_handle_t hd = resp_arg->hd;
int fd = resp_arg->fd;
httpd_ws_frame_t ws_pkt;
memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t));
ws_pkt.payload = (uint8_t*)data;
ws_pkt.len = strlen(data);
ws_pkt.type = HTTPD_WS_TYPE_TEXT;
httpd_ws_send_frame_async(hd, fd, &ws_pkt);
free(resp_arg);
}
static esp_err_t trigger_async_send(httpd_handle_t handle, httpd_req_t *req)
{
struct async_resp_arg *resp_arg = malloc(sizeof(struct async_resp_arg));
resp_arg->hd = req->handle;
resp_arg->fd = httpd_req_to_sockfd(req);
return httpd_queue_work(handle, ws_async_send, resp_arg);
}
/*
* This handler echos back the received ws data
* and triggers an async send if certain message received
*/
static esp_err_t echo_handler(httpd_req_t *req)
{
uint8_t buf[128] = { 0 };
httpd_ws_frame_t ws_pkt;
memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t));
ws_pkt.payload = buf;
ws_pkt.type = HTTPD_WS_TYPE_TEXT;
esp_err_t ret = httpd_ws_recv_frame(req, &ws_pkt, 128);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "httpd_ws_recv_frame failed with %d", ret);
return ret;
}
ESP_LOGI(TAG, "Got packet with message: %s", ws_pkt.payload);
ESP_LOGI(TAG, "Packet type: %d", ws_pkt.type);
if (ws_pkt.type == HTTPD_WS_TYPE_TEXT &&
strcmp((char*)ws_pkt.payload,"Trigger async") == 0) {
return trigger_async_send(req->handle, req);
}
ret = ret ? : httpd_ws_send_frame(req, &ws_pkt);
ret = httpd_ws_send_frame(req, &ws_pkt);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "httpd_ws_send_frame failed with %d", ret);
}
return ret;
}
static const httpd_uri_t ws = {
.uri = "/ws",
.method = HTTP_GET,
.handler = ws_ping_handler,
.handler = echo_handler,
.user_ctx = NULL,
.is_websocket = true
};
@@ -59,7 +108,7 @@ static httpd_handle_t start_webserver(void)
// Start the httpd server
ESP_LOGI(TAG, "Starting server on port: '%d'", config.server_port);
if (httpd_start(&server, &config) == ESP_OK) {
// Set URI handlers
// Registering the ws handler
ESP_LOGI(TAG, "Registering URI handlers");
httpd_register_uri_handler(server, &ws);
return server;
@@ -1,2 +1 @@
CONFIG_LOG_DEFAULT_LEVEL_DEBUG=y
@@ -0,0 +1 @@
CONFIG_HTTPD_WS_SUPPORT=y
@@ -17,7 +17,6 @@
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
from builtins import range
import re
from tiny_test_fw import Utility
import ttfw_idf
@@ -26,6 +25,7 @@ import six
import socket
import hashlib
import base64
import struct
OPCODE_TEXT = 0x1
@@ -54,9 +54,9 @@ class WsClient:
MAGIC_STRING = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
client_key = self.client_key + MAGIC_STRING
expected_accept = base64.standard_b64encode(hashlib.sha1(client_key.encode()).digest())
request = 'GET /ws HTTP/1.1\r\nHost: localhost\r\nUpgrade: websocket\r\nConnection: ' \
'Upgrade\r\nSec-WebSocket-Key: {}\r\n' \
'Sec-WebSocket-Version: 13\r\n\r\n'.format(self.client_key)
request = ('GET /ws HTTP/1.1\r\nHost: localhost\r\nUpgrade: websocket\r\nConnection: '
'Upgrade\r\nSec-WebSocket-Key: {}\r\n'
'Sec-WebSocket-Version: 13\r\n\r\n'.format(self.client_key))
self.socket.send(request.encode('utf-8'))
response = self.socket.recv(1024)
ws_accept = re.search(b'Sec-WebSocket-Accept: (.*)\r\n', response, re.IGNORECASE)
@@ -66,14 +66,11 @@ class WsClient:
raise("Unexpected Sec-WebSocket-Accept, handshake response: {}".format(response))
def _masked(self, data):
mask_key = os.urandom(4)
out = mask_key
for i in range(len(data)):
if six.PY3:
out += (data[i] ^ mask_key[i % 4]).to_bytes(1, byteorder="little")
else:
out += chr(ord(data[i]) ^ ord(mask_key[i % 4]))
return out
mask = struct.unpack('B' * 4, os.urandom(4))
out = list(mask)
for i, d in enumerate(struct.unpack('B' * len(data), data)):
out.append(d ^ mask[i % 4])
return struct.pack('B' * len(out), *out)
def _ws_encode(self, data="", opcode=OPCODE_TEXT, mask=1):
data = data.encode('utf-8')
@@ -109,7 +106,7 @@ def test_examples_protocol_http_ws_echo_server(env, extra_data):
binary_file = os.path.join(dut1.app.binary_path, "ws_echo_server.bin")
bin_size = os.path.getsize(binary_file)
ttfw_idf.log_performance("http_ws_server_bin_size", "{}KB".format(bin_size // 1024))
ttfw_idf.check_performance("http_ws_server_bin_size", bin_size // 1024)
ttfw_idf.check_performance("http_ws_server_bin_size", bin_size // 1024, dut1.TARGET)
# Upload binary and start testing
Utility.console_log("Starting ws-echo-server test app based on http_server")
@@ -117,19 +114,19 @@ def test_examples_protocol_http_ws_echo_server(env, extra_data):
# Parse IP address of STA
Utility.console_log("Waiting to connect with AP")
got_ip = dut1.expect(re.compile(r"(?:[\s\S]*)IPv4 address: (\d+.\d+.\d+.\d+)"), timeout=30)[0]
got_port = dut1.expect(re.compile(r"(?:[\s\S]*)Starting server on port: '(\d+)'"), timeout=30)[0]
got_ip = dut1.expect(re.compile(r"(?:[\s\S]*)IPv4 address: (\d+.\d+.\d+.\d+)"), timeout=60)[0]
got_port = dut1.expect(re.compile(r"(?:[\s\S]*)Starting server on port: '(\d+)'"), timeout=60)[0]
Utility.console_log("Got IP : " + got_ip)
Utility.console_log("Got Port : " + got_port)
# Start ws server test
with WsClient(got_ip, 80) as ws:
with WsClient(got_ip, int(got_port)) as ws:
DATA = 'Espressif'
for expected_opcode in [OPCODE_TEXT, OPCODE_BIN, OPCODE_PING]:
Utility.console_log("Testing opcode {}".format(expected_opcode))
ws.write(data=DATA, opcode=expected_opcode)
opcode, data = ws.read()
Utility.console_log("Testing opcode {}: Received opcode:{}, data:{}".format(expected_opcode, opcode, data))
if expected_opcode == OPCODE_PING:
dut1.expect("Got a WS PING frame, Replying PONG")
if opcode != OPCODE_PONG or data != DATA:
@@ -139,6 +136,11 @@ def test_examples_protocol_http_ws_echo_server(env, extra_data):
dut_opcode = int(dut1.expect(re.compile(r"Packet type: ([0-9]*)"))[0])
if opcode != expected_opcode or data != DATA or opcode != dut_opcode or data != dut_data:
raise RuntimeError("Failed to receive correct opcode:{} or data:{}".format(opcode, data))
ws.write(data="Trigger async", opcode=OPCODE_TEXT)
opcode, data = ws.read()
Utility.console_log("Testing async send: Received opcode:{}, data:{}".format(opcode, data))
if opcode != OPCODE_TEXT or data != "Async data":
raise RuntimeError("Failed to receive correct opcode:{} or data:{}".format(opcode, data))
if __name__ == '__main__':