http_server: websocket server to support async send
This commit is contained in:
parent
d7b3a051f0
commit
1b842ce1a8
|
@ -41,7 +41,7 @@ menu "HTTP Server"
|
||||||
|
|
||||||
config HTTPD_WS_SUPPORT
|
config HTTPD_WS_SUPPORT
|
||||||
bool "WebSocket server support"
|
bool "WebSocket server support"
|
||||||
default y
|
default n
|
||||||
help
|
help
|
||||||
This sets the WebSocket server support.
|
This sets the WebSocket server support.
|
||||||
|
|
||||||
|
|
|
@ -1514,6 +1514,23 @@ esp_err_t httpd_ws_recv_frame(httpd_req_t *req, httpd_ws_frame_t *pkt, size_t ma
|
||||||
*/
|
*/
|
||||||
esp_err_t httpd_ws_send_frame(httpd_req_t *req, httpd_ws_frame_t *pkt);
|
esp_err_t httpd_ws_send_frame(httpd_req_t *req, httpd_ws_frame_t *pkt);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Low level send of a WebSocket frame out of the scope of current request
|
||||||
|
* using internally configured httpd send function
|
||||||
|
*
|
||||||
|
* This API should rarely be called directly, with an exception of asynchronous send using httpd_queue_work.
|
||||||
|
*
|
||||||
|
* @param[in] hd Server instance data
|
||||||
|
* @param[in] fd Socket descriptor for sending data
|
||||||
|
* @param[in] frame WebSocket frame
|
||||||
|
* @return
|
||||||
|
* - ESP_OK : On successful
|
||||||
|
* - ESP_FAIL : When socket errors occurs
|
||||||
|
* - ESP_ERR_INVALID_STATE : Handshake was already done beforehand
|
||||||
|
* - ESP_ERR_INVALID_ARG : Argument is invalid (null or non-WebSocket)
|
||||||
|
*/
|
||||||
|
esp_err_t httpd_ws_send_frame_async(httpd_handle_t hd, int fd, httpd_ws_frame_t *frame);
|
||||||
|
|
||||||
#endif /* CONFIG_HTTPD_WS_SUPPORT */
|
#endif /* CONFIG_HTTPD_WS_SUPPORT */
|
||||||
/** End of WebSocket related stuff
|
/** End of WebSocket related stuff
|
||||||
* @}
|
* @}
|
||||||
|
|
|
@ -690,7 +690,9 @@ static void init_req_aux(struct httpd_req_aux *ra, httpd_config_t *config)
|
||||||
ra->first_chunk_sent = 0;
|
ra->first_chunk_sent = 0;
|
||||||
ra->req_hdrs_count = 0;
|
ra->req_hdrs_count = 0;
|
||||||
ra->resp_hdrs_count = 0;
|
ra->resp_hdrs_count = 0;
|
||||||
|
#if CONFIG_HTTPD_WS_SUPPORT
|
||||||
ra->ws_handshake_detect = false;
|
ra->ws_handshake_detect = false;
|
||||||
|
#endif
|
||||||
memset(ra->resp_hdrs, 0, config->max_resp_headers * sizeof(struct resp_hdr));
|
memset(ra->resp_hdrs, 0, config->max_resp_headers * sizeof(struct resp_hdr));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -703,11 +705,13 @@ static void httpd_req_cleanup(httpd_req_t *r)
|
||||||
httpd_sess_free_ctx(ra->sd->ctx, ra->sd->free_ctx);
|
httpd_sess_free_ctx(ra->sd->ctx, ra->sd->free_ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if CONFIG_HTTPD_WS_SUPPORT
|
||||||
/* Close the socket when a WebSocket Close request is received */
|
/* Close the socket when a WebSocket Close request is received */
|
||||||
if (ra->sd->ws_close) {
|
if (ra->sd->ws_close) {
|
||||||
ESP_LOGD(TAG, LOG_FMT("Try closing WS connection at FD: %d"), ra->sd->fd);
|
ESP_LOGD(TAG, LOG_FMT("Try closing WS connection at FD: %d"), ra->sd->fd);
|
||||||
httpd_sess_trigger_close(r->handle, ra->sd->fd);
|
httpd_sess_trigger_close(r->handle, ra->sd->fd);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
/* Retrieve session info from the request into the socket database. */
|
/* Retrieve session info from the request into the socket database. */
|
||||||
ra->sd->ctx = r->sess_ctx;
|
ra->sd->ctx = r->sess_ctx;
|
||||||
|
|
|
@ -279,7 +279,6 @@ esp_err_t httpd_uri(struct httpd_data *hd)
|
||||||
{
|
{
|
||||||
httpd_uri_t *uri = NULL;
|
httpd_uri_t *uri = NULL;
|
||||||
httpd_req_t *req = &hd->hd_req;
|
httpd_req_t *req = &hd->hd_req;
|
||||||
struct httpd_req_aux *aux = req->aux;
|
|
||||||
struct http_parser_url *res = &hd->hd_req_aux.url_parse_res;
|
struct http_parser_url *res = &hd->hd_req_aux.url_parse_res;
|
||||||
|
|
||||||
/* For conveying URI not found/method not allowed */
|
/* For conveying URI not found/method not allowed */
|
||||||
|
@ -313,6 +312,7 @@ esp_err_t httpd_uri(struct httpd_data *hd)
|
||||||
|
|
||||||
/* Final step for a WebSocket handshake verification */
|
/* Final step for a WebSocket handshake verification */
|
||||||
#ifdef CONFIG_HTTPD_WS_SUPPORT
|
#ifdef CONFIG_HTTPD_WS_SUPPORT
|
||||||
|
struct httpd_req_aux *aux = req->aux;
|
||||||
if (uri->is_websocket && aux->ws_handshake_detect && uri->method == HTTP_GET) {
|
if (uri->is_websocket && aux->ws_handshake_detect && uri->method == HTTP_GET) {
|
||||||
ESP_LOGD(TAG, LOG_FMT("Responding WS handshake to sock %d"), aux->sd->fd);
|
ESP_LOGD(TAG, LOG_FMT("Responding WS handshake to sock %d"), aux->sd->fd);
|
||||||
esp_err_t ret = httpd_ws_respond_server_handshake(&hd->hd_req);
|
esp_err_t ret = httpd_ws_respond_server_handshake(&hd->hd_req);
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
|
|
||||||
#ifdef CONFIG_HTTPD_WS_SUPPORT
|
#ifdef CONFIG_HTTPD_WS_SUPPORT
|
||||||
|
|
||||||
#define TAG "httpd_ws"
|
static const char *TAG="httpd_ws";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Bit masks for WebSocket frames.
|
* Bit masks for WebSocket frames.
|
||||||
|
@ -266,6 +266,11 @@ esp_err_t httpd_ws_send_frame(httpd_req_t *req, httpd_ws_frame_t *frame)
|
||||||
if (ret != ESP_OK) {
|
if (ret != ESP_OK) {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
return httpd_ws_send_frame_async(req->handle, httpd_req_to_sockfd(req), frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t httpd_ws_send_frame_async(httpd_handle_t hd, int fd, httpd_ws_frame_t *frame)
|
||||||
|
{
|
||||||
if (!frame) {
|
if (!frame) {
|
||||||
ESP_LOGW(TAG, LOG_FMT("Argument is invalid"));
|
ESP_LOGW(TAG, LOG_FMT("Argument is invalid"));
|
||||||
return ESP_ERR_INVALID_ARG;
|
return ESP_ERR_INVALID_ARG;
|
||||||
|
@ -299,15 +304,20 @@ esp_err_t httpd_ws_send_frame(httpd_req_t *req, httpd_ws_frame_t *frame)
|
||||||
/* WebSocket server does not required to mask response payload, so leave the MASK bit as 0. */
|
/* WebSocket server does not required to mask response payload, so leave the MASK bit as 0. */
|
||||||
header_buf[1] &= (~HTTPD_WS_MASK_BIT);
|
header_buf[1] &= (~HTTPD_WS_MASK_BIT);
|
||||||
|
|
||||||
|
struct sock_db *sess = httpd_sess_get(hd, fd);
|
||||||
|
if (!sess) {
|
||||||
|
return ESP_ERR_INVALID_ARG;
|
||||||
|
}
|
||||||
|
|
||||||
/* Send off header */
|
/* Send off header */
|
||||||
if (httpd_send(req, (const char *)header_buf, tx_len) < 0) {
|
if (sess->send_fn(hd, fd, (const char *)header_buf, tx_len, 0) < 0) {
|
||||||
ESP_LOGW(TAG, LOG_FMT("Failed to send WS header"));
|
ESP_LOGW(TAG, LOG_FMT("Failed to send WS header"));
|
||||||
return ESP_FAIL;
|
return ESP_FAIL;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Send off payload */
|
/* Send off payload */
|
||||||
if(frame->len > 0 && frame->payload != NULL) {
|
if(frame->len > 0 && frame->payload != NULL) {
|
||||||
if (httpd_send(req, (const char *)frame->payload, frame->len) < 0) {
|
if (sess->send_fn(hd, fd, (const char *)frame->payload, frame->len, 0) < 0) {
|
||||||
ESP_LOGW(TAG, LOG_FMT("Failed to send WS payload"));
|
ESP_LOGW(TAG, LOG_FMT("Failed to send WS payload"));
|
||||||
return ESP_FAIL;
|
return ESP_FAIL;
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,30 +22,79 @@
|
||||||
|
|
||||||
/* A simple example that demonstrates using websocket echo server
|
/* A simple example that demonstrates using websocket echo server
|
||||||
*/
|
*/
|
||||||
|
|
||||||
static const char *TAG = "ws_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 };
|
uint8_t buf[128] = { 0 };
|
||||||
httpd_ws_frame_t ws_pkt;
|
httpd_ws_frame_t ws_pkt;
|
||||||
memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t));
|
memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t));
|
||||||
ws_pkt.payload = buf;
|
ws_pkt.payload = buf;
|
||||||
ws_pkt.type = HTTPD_WS_TYPE_TEXT;
|
ws_pkt.type = HTTPD_WS_TYPE_TEXT;
|
||||||
|
|
||||||
esp_err_t ret = httpd_ws_recv_frame(req, &ws_pkt, 128);
|
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, "Got packet with message: %s", ws_pkt.payload);
|
||||||
ESP_LOGI(TAG, "Packet type: %d", ws_pkt.type);
|
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;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static const httpd_uri_t ws = {
|
static const httpd_uri_t ws = {
|
||||||
.uri = "/ws",
|
.uri = "/ws",
|
||||||
.method = HTTP_GET,
|
.method = HTTP_GET,
|
||||||
.handler = ws_ping_handler,
|
.handler = echo_handler,
|
||||||
.user_ctx = NULL,
|
.user_ctx = NULL,
|
||||||
.is_websocket = true
|
.is_websocket = true
|
||||||
};
|
};
|
||||||
|
@ -59,7 +108,7 @@ static httpd_handle_t start_webserver(void)
|
||||||
// Start the httpd server
|
// Start the httpd server
|
||||||
ESP_LOGI(TAG, "Starting server on port: '%d'", config.server_port);
|
ESP_LOGI(TAG, "Starting server on port: '%d'", config.server_port);
|
||||||
if (httpd_start(&server, &config) == ESP_OK) {
|
if (httpd_start(&server, &config) == ESP_OK) {
|
||||||
// Set URI handlers
|
// Registering the ws handler
|
||||||
ESP_LOGI(TAG, "Registering URI handlers");
|
ESP_LOGI(TAG, "Registering URI handlers");
|
||||||
httpd_register_uri_handler(server, &ws);
|
httpd_register_uri_handler(server, &ws);
|
||||||
return server;
|
return server;
|
||||||
|
|
|
@ -1,2 +1 @@
|
||||||
CONFIG_LOG_DEFAULT_LEVEL_DEBUG=y
|
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 division
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
from builtins import range
|
|
||||||
import re
|
import re
|
||||||
from tiny_test_fw import Utility
|
from tiny_test_fw import Utility
|
||||||
import ttfw_idf
|
import ttfw_idf
|
||||||
|
@ -26,6 +25,7 @@ import six
|
||||||
import socket
|
import socket
|
||||||
import hashlib
|
import hashlib
|
||||||
import base64
|
import base64
|
||||||
|
import struct
|
||||||
|
|
||||||
|
|
||||||
OPCODE_TEXT = 0x1
|
OPCODE_TEXT = 0x1
|
||||||
|
@ -54,9 +54,9 @@ class WsClient:
|
||||||
MAGIC_STRING = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
|
MAGIC_STRING = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
|
||||||
client_key = self.client_key + MAGIC_STRING
|
client_key = self.client_key + MAGIC_STRING
|
||||||
expected_accept = base64.standard_b64encode(hashlib.sha1(client_key.encode()).digest())
|
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: ' \
|
request = ('GET /ws HTTP/1.1\r\nHost: localhost\r\nUpgrade: websocket\r\nConnection: '
|
||||||
'Upgrade\r\nSec-WebSocket-Key: {}\r\n' \
|
'Upgrade\r\nSec-WebSocket-Key: {}\r\n'
|
||||||
'Sec-WebSocket-Version: 13\r\n\r\n'.format(self.client_key)
|
'Sec-WebSocket-Version: 13\r\n\r\n'.format(self.client_key))
|
||||||
self.socket.send(request.encode('utf-8'))
|
self.socket.send(request.encode('utf-8'))
|
||||||
response = self.socket.recv(1024)
|
response = self.socket.recv(1024)
|
||||||
ws_accept = re.search(b'Sec-WebSocket-Accept: (.*)\r\n', response, re.IGNORECASE)
|
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))
|
raise("Unexpected Sec-WebSocket-Accept, handshake response: {}".format(response))
|
||||||
|
|
||||||
def _masked(self, data):
|
def _masked(self, data):
|
||||||
mask_key = os.urandom(4)
|
mask = struct.unpack('B' * 4, os.urandom(4))
|
||||||
out = mask_key
|
out = list(mask)
|
||||||
for i in range(len(data)):
|
for i, d in enumerate(struct.unpack('B' * len(data), data)):
|
||||||
if six.PY3:
|
out.append(d ^ mask[i % 4])
|
||||||
out += (data[i] ^ mask_key[i % 4]).to_bytes(1, byteorder="little")
|
return struct.pack('B' * len(out), *out)
|
||||||
else:
|
|
||||||
out += chr(ord(data[i]) ^ ord(mask_key[i % 4]))
|
|
||||||
return out
|
|
||||||
|
|
||||||
def _ws_encode(self, data="", opcode=OPCODE_TEXT, mask=1):
|
def _ws_encode(self, data="", opcode=OPCODE_TEXT, mask=1):
|
||||||
data = data.encode('utf-8')
|
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")
|
binary_file = os.path.join(dut1.app.binary_path, "ws_echo_server.bin")
|
||||||
bin_size = os.path.getsize(binary_file)
|
bin_size = os.path.getsize(binary_file)
|
||||||
ttfw_idf.log_performance("http_ws_server_bin_size", "{}KB".format(bin_size // 1024))
|
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
|
# Upload binary and start testing
|
||||||
Utility.console_log("Starting ws-echo-server test app based on http_server")
|
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
|
# Parse IP address of STA
|
||||||
Utility.console_log("Waiting to connect with AP")
|
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_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=30)[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 IP : " + got_ip)
|
||||||
Utility.console_log("Got Port : " + got_port)
|
Utility.console_log("Got Port : " + got_port)
|
||||||
|
|
||||||
# Start ws server test
|
# Start ws server test
|
||||||
with WsClient(got_ip, 80) as ws:
|
with WsClient(got_ip, int(got_port)) as ws:
|
||||||
DATA = 'Espressif'
|
DATA = 'Espressif'
|
||||||
for expected_opcode in [OPCODE_TEXT, OPCODE_BIN, OPCODE_PING]:
|
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)
|
ws.write(data=DATA, opcode=expected_opcode)
|
||||||
opcode, data = ws.read()
|
opcode, data = ws.read()
|
||||||
|
Utility.console_log("Testing opcode {}: Received opcode:{}, data:{}".format(expected_opcode, opcode, data))
|
||||||
if expected_opcode == OPCODE_PING:
|
if expected_opcode == OPCODE_PING:
|
||||||
dut1.expect("Got a WS PING frame, Replying PONG")
|
dut1.expect("Got a WS PING frame, Replying PONG")
|
||||||
if opcode != OPCODE_PONG or data != DATA:
|
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])
|
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:
|
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))
|
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__':
|
if __name__ == '__main__':
|
||||||
|
|
Loading…
Reference in a new issue