Skip to content

Commit f12114e

Browse files
another websocket library example
1 parent ba9e55e commit f12114e

File tree

2 files changed

+253
-0
lines changed

2 files changed

+253
-0
lines changed
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# test of https://github.com/Vovaman/micropython_async_websocket_client/
2+
# install with mip
3+
4+
import socket
5+
import asyncio as a
6+
import binascii as b
7+
import random as r
8+
from collections import namedtuple
9+
import re
10+
import struct
11+
import ssl
12+
13+
from ws import AsyncWebsocketClient
14+
15+
async def websocket_example():
16+
# Initialize the WebSocket client
17+
aws = AsyncWebsocketClient(ms_delay_for_read=10)
18+
19+
try:
20+
# Connect to echo.websocket.events
21+
await aws.handshake("wss://echo.websocket.events")
22+
print("Connected to WebSocket server")
23+
24+
# Send a test message
25+
test_message = "Hello, WebSocket!"
26+
print(f"Sending: {test_message}")
27+
await aws.send(test_message)
28+
29+
# Receive the echoed response
30+
response = await aws.recv()
31+
print(f"Received: {response}")
32+
33+
# Send a ping
34+
aws.write_frame(ws.OP_PING, b"ping")
35+
print("Sent ping")
36+
37+
# Wait for pong or other messages
38+
response = await aws.recv()
39+
if response is None:
40+
print("Received pong or connection closed")
41+
else:
42+
print(f"Received: {response}")
43+
44+
except Exception as e:
45+
print(f"Error: {e}")
46+
finally:
47+
# Clean up
48+
await aws.close()
49+
print("Connection closed")
50+
51+
# Run the example
52+
a.run(websocket_example())
Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
import socket
2+
import ssl
3+
import ubinascii
4+
from websocket import websocket
5+
import select
6+
import urandom
7+
8+
# Function to send a masked WebSocket text frame
9+
def ws_send_text(sock, message):
10+
data = message.encode()
11+
length = len(data)
12+
13+
frame = bytearray()
14+
frame.append(0x81) # FIN=1, opcode=0x1 (text)
15+
mask_bit = 0x80
16+
mask_key = bytearray(urandom.getrandbits(32).to_bytes(4, 'big'))
17+
18+
if length <= 125:
19+
frame.append(mask_bit | length)
20+
elif length <= 65535:
21+
frame.append(mask_bit | 126)
22+
frame.extend(length.to_bytes(2, 'big'))
23+
else:
24+
frame.append(mask_bit | 127)
25+
frame.extend(length.to_bytes(8, 'big'))
26+
27+
frame.extend(mask_key)
28+
masked_data = bytearray(data)
29+
for i in range(len(data)):
30+
masked_data[i] ^= mask_key[i % 4]
31+
frame.extend(masked_data)
32+
return sock.write(frame)
33+
34+
# Resolve hostname
35+
host = 'echo.websocket.events'
36+
port = 443
37+
handshake_path = '/' # Matches aiohttp example
38+
# Fallback: ws.postman-echo.com
39+
host = 'ws.postman-echo.com'
40+
handshake_path = '/raw'
41+
42+
try:
43+
addr_info = socket.getaddrinfo(host, port)[0][-1]
44+
print('Resolved address:', addr_info)
45+
except Exception as e:
46+
print('DNS resolution failed:', e)
47+
raise
48+
49+
# Create and connect socket
50+
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
51+
try:
52+
sock.connect(addr_info)
53+
print('Socket connected')
54+
except Exception as e:
55+
print('Connect failed:', e)
56+
sock.close()
57+
raise
58+
59+
# Wrap socket with SSL
60+
try:
61+
ssl_sock = ssl.wrap_socket(sock, server_hostname=host)
62+
print('SSL connection established')
63+
print('SSL cipher:', ssl_sock.cipher())
64+
except Exception as e:
65+
print('SSL wrap failed:', e)
66+
sock.close()
67+
raise
68+
69+
# Set socket to non-blocking
70+
ssl_sock.setblocking(False)
71+
72+
# Perform WebSocket handshake
73+
key = ubinascii.b2a_base64(b'random_bytes_here').strip()
74+
handshake = (
75+
'GET {} HTTP/1.1\r\n'
76+
'Host: {}\r\n'
77+
'Upgrade: websocket\r\n'
78+
'Connection: Upgrade\r\n'
79+
'Sec-WebSocket-Key: {}\r\n'
80+
'Sec-WebSocket-Version: 13\r\n'
81+
'\r\n'
82+
).format(handshake_path, host, key.decode())
83+
84+
try:
85+
bytes_written = ssl_sock.write(handshake.encode())
86+
print('Handshake sent, bytes written:', bytes_written)
87+
except Exception as e:
88+
print('Failed to send handshake:', e)
89+
ssl_sock.close()
90+
raise
91+
92+
# Read HTTP response until \r\n\r\n
93+
response_bytes = bytearray()
94+
poller = select.poll()
95+
poller.register(ssl_sock, select.POLLIN)
96+
max_polls = 100 # 10s timeout (100 * 100ms)
97+
for poll_count in range(max_polls):
98+
events = poller.poll(100)
99+
print('Poll', poll_count + 1, 'events:', events)
100+
if events:
101+
chunk = ssl_sock.read(128)
102+
if chunk is None:
103+
print('Read returned None, continuing')
104+
continue
105+
if not chunk:
106+
print('No more data received (EOF)')
107+
break
108+
response_bytes.extend(chunk)
109+
print('Received chunk, length:', len(chunk))
110+
if b'\r\n\r\n' in response_bytes:
111+
http_response_bytes = response_bytes[:response_bytes.find(b'\r\n\r\n') + 4]
112+
try:
113+
response = http_response_bytes.decode('utf-8')
114+
if '101 Switching Protocols' not in response:
115+
raise Exception('Handshake failed')
116+
print('Handshake successful')
117+
break
118+
except UnicodeError as e:
119+
print('UnicodeError:', e)
120+
ssl_sock.close()
121+
raise Exception('Failed to decode HTTP response')
122+
else:
123+
print('Poll', poll_count + 1, 'no data')
124+
else:
125+
ssl_sock.close()
126+
print('Handshake timeout: No response received after {} seconds ({} bytes received)'.format(max_polls * 0.1, len(response_bytes)))
127+
raise Exception('Handshake timeout')
128+
129+
# Create WebSocket object
130+
ws = websocket(ssl_sock, True)
131+
print('WebSocket object created')
132+
133+
# Send and receive data
134+
try:
135+
# This doesn't work because the websocket module fails to set the mask bit
136+
# and apply a random 4-byte mask key as required by the WebSocket protocol
137+
# for client-to-server frames, causing the server to close the connection
138+
# with an "incorrect mask flag" error (status code 1002).
139+
# bytes_written = ws.write('hello world!\r\n')
140+
# print('Sent message, bytes written:', bytes_written)
141+
142+
# Proper, working code using manual frame masking
143+
bytes_written = ws_send_text(ssl_sock, 'hello world!\r\n')
144+
print('Sent message, bytes written:', bytes_written)
145+
except Exception as e:
146+
print('Failed to send message:', e)
147+
ws.close()
148+
raise
149+
150+
# Debug: Read raw socket data
151+
try:
152+
events = poller.poll(500)
153+
if events:
154+
raw_data = ssl_sock.read(1024)
155+
print('Raw socket data:', raw_data)
156+
if raw_data:
157+
print('Raw socket data hex:', raw_data.hex())
158+
else:
159+
print('No raw socket data available')
160+
except Exception as e:
161+
print('Raw socket read error:', e)
162+
163+
# Read WebSocket messages
164+
max_attempts = 5 # Reduced to 5 attempts (2.5s with 500ms polls)
165+
for attempt in range(max_attempts):
166+
events = poller.poll(500)
167+
print('Read poll attempt', attempt + 1, 'events:', events)
168+
if events:
169+
try:
170+
data = ws.read(1024)
171+
if data is None:
172+
print('Read attempt', attempt + 1, 'returned None')
173+
continue
174+
print('Read attempt', attempt + 1, 'received:', data)
175+
if data:
176+
continue # Keep reading for more messages
177+
except Exception as e:
178+
print('Read attempt', attempt + 1, 'error:', e)
179+
else:
180+
print('Read attempt', attempt + 1, 'no data')
181+
else:
182+
print('Read timeout: No response received after {} attempts ({} seconds)'.format(max_attempts, max_attempts * 0.5))
183+
184+
# Send close message
185+
try:
186+
# This doesn't work because the websocket module fails to set the mask bit
187+
# and apply a random 4-byte mask key as required by the WebSocket protocol
188+
# for client-to-server frames, causing the server to close the connection
189+
# with an "incorrect mask flag" error (status code 1002).
190+
# bytes_written = ws.write('close\r\n')
191+
# print('Sent close, bytes written:', bytes_written)
192+
193+
# Proper, working code using manual frame masking
194+
bytes_written = ws_send_text(ssl_sock, 'close\r\n')
195+
print('Sent close, bytes written:', bytes_written)
196+
except Exception as e:
197+
print('Failed to send close:', e)
198+
199+
# Close connection
200+
ws.close()
201+
print('Connection closed')

0 commit comments

Comments
 (0)