README.md
1# Websocket echo server
2
3(See the README.md file in the upper level 'examples' directory for more information about examples.)
4This example demonstrates the HTTPD server using the WebSocket feature.
5
6## How to Use Example
7
8The example starts a websocket server on a local network. You need a websocket client to interact with the server (an example test
9ws_server_example_test.py could be used as the simple websocket client). If you run ws_server_example_test.py and get
10`ModuleNotFoundError: No module named 'websocket'`, then please install `websocket` by running `python -m pip install websocket-client`.
11
12The server registers websocket handler which echoes back the received WebSocket frame. It also demonstrates
13use of asynchronous send, which is triggered on reception of a certain message.
14
15### Websocket support in `http_server`
16
17Websocket echo server is build on top of the HTTP server component using [Websocket server](https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/protocols/esp_http_server.html#websocket-server) configuration.
18This feature is very limited, and a special care must be taken while implementing websocket URI handlers.
19
20#### Configure URI handler
21
22We register the URI handler with the standard API `httpd_register_uri_handler()` with `is_websocket` enabled and other optional parameters:
23
24```c
25static const httpd_uri_t ws_uri_handler_options = {
26 ... // httpd options
27
28 .is_websocket = true, // Mandatory: set to `true` to handler websocket protocol
29 .handle_ws_control_frames = false, // Optional: set to `true` for the handler to receive control packets, too
30 .supported_subprotocol = "chat", // Optional: set supported subprotocol for this handler
31};
32
33```
34
35#### Implement URI handler
36
37The URI handler is called on every URI request, but also before the websocket handshake, so it is very important to check the request type before reading websocket frame:
38
39```c
40 // beginning of the ws URI handler
41 if (req->method == HTTP_GET) {
42 // action before ws handshake
43 return ESP_OK;
44 }
45 // action after the handshake (read frames)
46```
47
48#### Handling incoming data
49
50To receive websocket frames, use `httpd_ws_recv_frame()` after the websocket handshake, with `httpd_ws_frame_t` parameters set accordingly:
51* `payload` to a valid buffer for the received data
52* `len` set to `0` for the first call of `httpd_ws_recv_frame()`. Note that this value is used to indicate the packet length has been read in the previous call if we use dynamic buffers.
53
54`httpd_ws_recv_frame` support two ways to get frame payload.
55* Static buffer -- Allocate maximum expected packet length (either statically or dynamically) and call `httpd_ws_recv_frame()` referencing this buffer and it's size. (Unnecessarily large buffers might cause memory waste)
56
57```
58#define MAX_PAYLOAD_LEN 128
59uint8_t buf[MAX_PAYLOAD_LEN] = { 0 };
60httpd_ws_frame_t ws_pkt;
61ws_pkt.payload = buf;
62httpd_ws_recv_frame(req, &ws_pkt, MAX_PAYLOAD_LEN);
63```
64* Dynamic buffer -- Refer to the examples, which receive websocket data in these three steps:
65 1) Call `httpd_ws_recv_frame()` with zero buffer size
66 2) Allocate the size based on the received packet length
67 3) Call `httpd_ws_recv_frame()` with the allocated buffer
68
69#### Handling outgoing data
70
71Please note that the WebSocket HTTP server does not automatically fragment messages.
72Each outgoing frame has the FIN flag set by default.
73In case an application wants to send fragmented data, it must be done manually by setting the
74`fragmented` option and using the `final` flag as described in [RFC6455, section 5.4](https://tools.ietf.org/html/rfc6455#section-5.4).
75
76
77### Hardware Required
78
79This example can be executed on any common development board, the only required interface is WiFi or Ethernet connection to a local network.
80
81### Configure the project
82
83* Open the project configuration menu (`idf.py menuconfig`)
84* Configure Wi-Fi or Ethernet under "Example Connection Configuration" menu. See "Establishing Wi-Fi or Ethernet Connection" section in [examples/protocols/README.md](../../README.md) for more details.
85
86### Build and Flash
87
88Build the project and flash it to the board, then run monitor tool to view serial output:
89
90```
91idf.py -p PORT flash monitor
92```
93
94(To exit the serial monitor, type ``Ctrl-]``.)
95
96See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
97
98## Example Output
99```
100I (4932) example_connect: Got IPv6 event!
101I (4942) example_connect: Connected to Espressif
102I (4942) example_connect: IPv4 address: 192.168.4.2
103I (4952) example_connect: IPv6 address: fe80:xxxx
104I (4962) ws_echo_server: Starting server on port: '80'
105I (4962) ws_echo_server: Registering URI handlers
106D (4962) httpd: httpd_thread: web server started
107D (4972) httpd: httpd_server: doing select maxfd+1 = 56
108D (4982) httpd_uri: httpd_register_uri_handler: [0] installed /ws
109D (17552) httpd: httpd_server: processing listen socket 54
110D (17552) httpd: httpd_accept_conn: newfd = 57
111D (17552) httpd_sess: httpd_sess_new: fd = 57
112D (17562) httpd: httpd_accept_conn: complete
113D (17562) httpd: httpd_server: doing select maxfd+1 = 58
114D (17572) httpd: httpd_server: processing socket 57
115D (17572) httpd_sess: httpd_sess_process: httpd_req_new
116D (17582) httpd_parse: httpd_req_new: New request, has WS? No, sd->ws_handler valid? No, sd->ws_close? No
117D (17592) httpd_txrx: httpd_recv_with_opt: requested length = 128
118D (17592) httpd_txrx: httpd_recv_with_opt: received length = 128
119D (17602) httpd_parse: read_block: received HTTP request block size = 128
120D (17612) httpd_parse: cb_url: message begin
121D (17612) httpd_parse: cb_url: processing url = /ws
122D (17622) httpd_parse: verify_url: received URI = /ws
123D (17622) httpd_parse: cb_header_field: headers begin
124D (17632) httpd_txrx: httpd_unrecv: length = 110
125D (17632) httpd_parse: pause_parsing: paused
126D (17632) httpd_parse: cb_header_field: processing field = Host
127D (17642) httpd_txrx: httpd_recv_with_opt: requested length = 128
128D (17652) httpd_txrx: httpd_recv_with_opt: pending length = 110
129D (17652) httpd_parse: read_block: received HTTP request block size = 110
130D (17662) httpd_parse: continue_parsing: skip pre-parsed data of size = 5
131D (17672) httpd_parse: continue_parsing: un-paused
132D (17682) httpd_parse: cb_header_field: processing field = Upgrade
133D (17682) httpd_parse: cb_header_value: processing value = websocket
134D (17692) httpd_parse: cb_header_field: processing field = Connection
135D (17702) httpd_parse: cb_header_value: processing value = Upgrade
136D (17702) httpd_parse: cb_header_field: processing field = Sec-WebSocket-Key
137D (17712) httpd_parse: cb_header_value: processing value = gfhjgfhjfj
138D (17722) httpd_parse: cb_header_field: processing field = Sec-WebSocket-Proto
139D (17722) httpd_parse: parse_block: parsed block size = 110
140D (17732) httpd_txrx: httpd_recv_with_opt: requested length = 128
141D (17742) httpd_txrx: httpd_recv_with_opt: received length = 40
142D (17742) httpd_parse: read_block: received HTTP request block size = 40
143D (17752) httpd_parse: cb_header_field: processing field = col
144D (17752) httpd_parse: cb_header_value: processing value = echo
145D (17762) httpd_parse: cb_header_field: processing field = Sec-WebSocket-Version
146D (17772) httpd_parse: cb_header_value: processing value = 13
147D (17772) httpd_parse: cb_headers_complete: bytes read = 169
148D (17782) httpd_parse: cb_headers_complete: content length = 0
149D (17792) httpd_parse: cb_headers_complete: Got an upgrade request
150D (17792) httpd_parse: pause_parsing: paused
151D (17802) httpd_parse: cb_no_body: message complete
152D (17802) httpd_parse: httpd_parse_req: parsing complete
153D (17812) httpd_uri: httpd_uri: request for /ws with type 1
154D (17812) httpd_uri: httpd_find_uri_handler: [0] = /ws
155D (17822) httpd_uri: httpd_uri: Responding WS handshake to sock 57
156D (17822) httpd_ws: httpd_ws_respond_server_handshake: Server key before encoding: gfhjgfhjfj258EAFA5-E914-47DA-95CA-C5AB0DC85B11
157D (17842) httpd_ws: httpd_ws_respond_server_handshake: Generated server key: Jg/fQVRsgwdDzYeG8yNBHRajUxw=
158D (17852) httpd_sess: httpd_sess_process: httpd_req_delete
159D (17852) httpd_sess: httpd_sess_process: success
160D (17862) httpd: httpd_server: doing select maxfd+1 = 58
161D (17892) httpd: httpd_server: processing socket 57
162D (17892) httpd_sess: httpd_sess_process: httpd_req_new
163D (17892) httpd_parse: httpd_req_new: New request, has WS? Yes, sd->ws_handler valid? Yes, sd->ws_close? No
164D (17902) httpd_parse: httpd_req_new: New WS request from existing socket
165D (17902) httpd_txrx: httpd_recv_with_opt: requested length = 1
166D (17912) httpd_txrx: httpd_recv_with_opt: received length = 1
167D (17912) httpd_ws: httpd_ws_get_frame_type: First byte received: 0x81
168D (17922) httpd_txrx: httpd_recv_with_opt: requested length = 1
169D (17932) httpd_txrx: httpd_recv_with_opt: received length = 1
170D (17932) httpd_txrx: httpd_recv_with_opt: requested length = 4
171D (17942) httpd_txrx: httpd_recv_with_opt: received length = 4
172D (17942) httpd_txrx: httpd_recv_with_opt: requested length = 13
173D (17952) httpd_txrx: httpd_recv_with_opt: received length = 13
174I (17962) ws_echo_server: Got packet with message: Trigger async
175I (17962) ws_echo_server: Packet type: 1
176D (17972) httpd_sess: httpd_sess_process: httpd_req_delete
177D (17972) httpd_sess: httpd_sess_process: success
178D (17982) httpd: httpd_server: doing select maxfd+1 = 58
179D (17982) httpd: httpd_server: processing ctrl message
180D (17992) httpd: httpd_process_ctrl_msg: work
181D (18002) httpd: httpd_server: doing select maxfd+1 = 58
182```
183
184See the README.md file in the upper level 'examples' directory for more information about examples.
185