You've already forked MicroPythonOS
mirror of
https://github.com/m5stack/MicroPythonOS.git
synced 2026-05-20 11:51:27 -07:00
another websocket library example
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
# test of https://github.com/Vovaman/micropython_async_websocket_client/
|
||||
# install with mip
|
||||
|
||||
import socket
|
||||
import asyncio as a
|
||||
import binascii as b
|
||||
import random as r
|
||||
from collections import namedtuple
|
||||
import re
|
||||
import struct
|
||||
import ssl
|
||||
|
||||
from ws import AsyncWebsocketClient
|
||||
|
||||
async def websocket_example():
|
||||
# Initialize the WebSocket client
|
||||
aws = AsyncWebsocketClient(ms_delay_for_read=10)
|
||||
|
||||
try:
|
||||
# Connect to echo.websocket.events
|
||||
await aws.handshake("wss://echo.websocket.events")
|
||||
print("Connected to WebSocket server")
|
||||
|
||||
# Send a test message
|
||||
test_message = "Hello, WebSocket!"
|
||||
print(f"Sending: {test_message}")
|
||||
await aws.send(test_message)
|
||||
|
||||
# Receive the echoed response
|
||||
response = await aws.recv()
|
||||
print(f"Received: {response}")
|
||||
|
||||
# Send a ping
|
||||
aws.write_frame(ws.OP_PING, b"ping")
|
||||
print("Sent ping")
|
||||
|
||||
# Wait for pong or other messages
|
||||
response = await aws.recv()
|
||||
if response is None:
|
||||
print("Received pong or connection closed")
|
||||
else:
|
||||
print(f"Received: {response}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error: {e}")
|
||||
finally:
|
||||
# Clean up
|
||||
await aws.close()
|
||||
print("Connection closed")
|
||||
|
||||
# Run the example
|
||||
a.run(websocket_example())
|
||||
@@ -0,0 +1,201 @@
|
||||
import socket
|
||||
import ssl
|
||||
import ubinascii
|
||||
from websocket import websocket
|
||||
import select
|
||||
import urandom
|
||||
|
||||
# Function to send a masked WebSocket text frame
|
||||
def ws_send_text(sock, message):
|
||||
data = message.encode()
|
||||
length = len(data)
|
||||
|
||||
frame = bytearray()
|
||||
frame.append(0x81) # FIN=1, opcode=0x1 (text)
|
||||
mask_bit = 0x80
|
||||
mask_key = bytearray(urandom.getrandbits(32).to_bytes(4, 'big'))
|
||||
|
||||
if length <= 125:
|
||||
frame.append(mask_bit | length)
|
||||
elif length <= 65535:
|
||||
frame.append(mask_bit | 126)
|
||||
frame.extend(length.to_bytes(2, 'big'))
|
||||
else:
|
||||
frame.append(mask_bit | 127)
|
||||
frame.extend(length.to_bytes(8, 'big'))
|
||||
|
||||
frame.extend(mask_key)
|
||||
masked_data = bytearray(data)
|
||||
for i in range(len(data)):
|
||||
masked_data[i] ^= mask_key[i % 4]
|
||||
frame.extend(masked_data)
|
||||
return sock.write(frame)
|
||||
|
||||
# Resolve hostname
|
||||
host = 'echo.websocket.events'
|
||||
port = 443
|
||||
handshake_path = '/' # Matches aiohttp example
|
||||
# Fallback: ws.postman-echo.com
|
||||
host = 'ws.postman-echo.com'
|
||||
handshake_path = '/raw'
|
||||
|
||||
try:
|
||||
addr_info = socket.getaddrinfo(host, port)[0][-1]
|
||||
print('Resolved address:', addr_info)
|
||||
except Exception as e:
|
||||
print('DNS resolution failed:', e)
|
||||
raise
|
||||
|
||||
# Create and connect socket
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
try:
|
||||
sock.connect(addr_info)
|
||||
print('Socket connected')
|
||||
except Exception as e:
|
||||
print('Connect failed:', e)
|
||||
sock.close()
|
||||
raise
|
||||
|
||||
# Wrap socket with SSL
|
||||
try:
|
||||
ssl_sock = ssl.wrap_socket(sock, server_hostname=host)
|
||||
print('SSL connection established')
|
||||
print('SSL cipher:', ssl_sock.cipher())
|
||||
except Exception as e:
|
||||
print('SSL wrap failed:', e)
|
||||
sock.close()
|
||||
raise
|
||||
|
||||
# Set socket to non-blocking
|
||||
ssl_sock.setblocking(False)
|
||||
|
||||
# Perform WebSocket handshake
|
||||
key = ubinascii.b2a_base64(b'random_bytes_here').strip()
|
||||
handshake = (
|
||||
'GET {} HTTP/1.1\r\n'
|
||||
'Host: {}\r\n'
|
||||
'Upgrade: websocket\r\n'
|
||||
'Connection: Upgrade\r\n'
|
||||
'Sec-WebSocket-Key: {}\r\n'
|
||||
'Sec-WebSocket-Version: 13\r\n'
|
||||
'\r\n'
|
||||
).format(handshake_path, host, key.decode())
|
||||
|
||||
try:
|
||||
bytes_written = ssl_sock.write(handshake.encode())
|
||||
print('Handshake sent, bytes written:', bytes_written)
|
||||
except Exception as e:
|
||||
print('Failed to send handshake:', e)
|
||||
ssl_sock.close()
|
||||
raise
|
||||
|
||||
# Read HTTP response until \r\n\r\n
|
||||
response_bytes = bytearray()
|
||||
poller = select.poll()
|
||||
poller.register(ssl_sock, select.POLLIN)
|
||||
max_polls = 100 # 10s timeout (100 * 100ms)
|
||||
for poll_count in range(max_polls):
|
||||
events = poller.poll(100)
|
||||
print('Poll', poll_count + 1, 'events:', events)
|
||||
if events:
|
||||
chunk = ssl_sock.read(128)
|
||||
if chunk is None:
|
||||
print('Read returned None, continuing')
|
||||
continue
|
||||
if not chunk:
|
||||
print('No more data received (EOF)')
|
||||
break
|
||||
response_bytes.extend(chunk)
|
||||
print('Received chunk, length:', len(chunk))
|
||||
if b'\r\n\r\n' in response_bytes:
|
||||
http_response_bytes = response_bytes[:response_bytes.find(b'\r\n\r\n') + 4]
|
||||
try:
|
||||
response = http_response_bytes.decode('utf-8')
|
||||
if '101 Switching Protocols' not in response:
|
||||
raise Exception('Handshake failed')
|
||||
print('Handshake successful')
|
||||
break
|
||||
except UnicodeError as e:
|
||||
print('UnicodeError:', e)
|
||||
ssl_sock.close()
|
||||
raise Exception('Failed to decode HTTP response')
|
||||
else:
|
||||
print('Poll', poll_count + 1, 'no data')
|
||||
else:
|
||||
ssl_sock.close()
|
||||
print('Handshake timeout: No response received after {} seconds ({} bytes received)'.format(max_polls * 0.1, len(response_bytes)))
|
||||
raise Exception('Handshake timeout')
|
||||
|
||||
# Create WebSocket object
|
||||
ws = websocket(ssl_sock, True)
|
||||
print('WebSocket object created')
|
||||
|
||||
# Send and receive data
|
||||
try:
|
||||
# This doesn't work because the websocket module fails to set the mask bit
|
||||
# and apply a random 4-byte mask key as required by the WebSocket protocol
|
||||
# for client-to-server frames, causing the server to close the connection
|
||||
# with an "incorrect mask flag" error (status code 1002).
|
||||
# bytes_written = ws.write('hello world!\r\n')
|
||||
# print('Sent message, bytes written:', bytes_written)
|
||||
|
||||
# Proper, working code using manual frame masking
|
||||
bytes_written = ws_send_text(ssl_sock, 'hello world!\r\n')
|
||||
print('Sent message, bytes written:', bytes_written)
|
||||
except Exception as e:
|
||||
print('Failed to send message:', e)
|
||||
ws.close()
|
||||
raise
|
||||
|
||||
# Debug: Read raw socket data
|
||||
try:
|
||||
events = poller.poll(500)
|
||||
if events:
|
||||
raw_data = ssl_sock.read(1024)
|
||||
print('Raw socket data:', raw_data)
|
||||
if raw_data:
|
||||
print('Raw socket data hex:', raw_data.hex())
|
||||
else:
|
||||
print('No raw socket data available')
|
||||
except Exception as e:
|
||||
print('Raw socket read error:', e)
|
||||
|
||||
# Read WebSocket messages
|
||||
max_attempts = 5 # Reduced to 5 attempts (2.5s with 500ms polls)
|
||||
for attempt in range(max_attempts):
|
||||
events = poller.poll(500)
|
||||
print('Read poll attempt', attempt + 1, 'events:', events)
|
||||
if events:
|
||||
try:
|
||||
data = ws.read(1024)
|
||||
if data is None:
|
||||
print('Read attempt', attempt + 1, 'returned None')
|
||||
continue
|
||||
print('Read attempt', attempt + 1, 'received:', data)
|
||||
if data:
|
||||
continue # Keep reading for more messages
|
||||
except Exception as e:
|
||||
print('Read attempt', attempt + 1, 'error:', e)
|
||||
else:
|
||||
print('Read attempt', attempt + 1, 'no data')
|
||||
else:
|
||||
print('Read timeout: No response received after {} attempts ({} seconds)'.format(max_attempts, max_attempts * 0.5))
|
||||
|
||||
# Send close message
|
||||
try:
|
||||
# This doesn't work because the websocket module fails to set the mask bit
|
||||
# and apply a random 4-byte mask key as required by the WebSocket protocol
|
||||
# for client-to-server frames, causing the server to close the connection
|
||||
# with an "incorrect mask flag" error (status code 1002).
|
||||
# bytes_written = ws.write('close\r\n')
|
||||
# print('Sent close, bytes written:', bytes_written)
|
||||
|
||||
# Proper, working code using manual frame masking
|
||||
bytes_written = ws_send_text(ssl_sock, 'close\r\n')
|
||||
print('Sent close, bytes written:', bytes_written)
|
||||
except Exception as e:
|
||||
print('Failed to send close:', e)
|
||||
|
||||
# Close connection
|
||||
ws.close()
|
||||
print('Connection closed')
|
||||
Reference in New Issue
Block a user