Skip to content

Commit 924e55a

Browse files
committed
extmod/webrepl: Allow the page to run from the device (over HTTP).
The device will respond to a non-WS request with a simple page that loads websocket_content.js from a static host (http or https). However, even if the resources are https, the page is still http and therefore allows requesting to a WS (not WSS) websocket on the device. Removed unused client_handshake from websocket_helper, and then merges the remainder of this file (server_handshake) into webrepl.py (to reduce firmware size). Also added the respond-as-HTTP handling to server_handshake. The default HTTP response is a simple page that sets the base URL and then loads webrepl_content.js which document.write's the actual HTML. Signed-off-by: Jim Mussared <[email protected]>
1 parent d2e4cf0 commit 924e55a

File tree

4 files changed

+107
-102
lines changed

4 files changed

+107
-102
lines changed

extmod/webrepl/manifest.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
freeze(".", ("webrepl.py", "webrepl_setup.py", "websocket_helper.py"))
1+
freeze(".", ("webrepl.py", "webrepl_setup.py"))

extmod/webrepl/webrepl.py

+106-15
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,93 @@
11
# This module should be imported from REPL, not run from command line.
2-
import socket
3-
import uos
2+
import binascii
3+
import hashlib
44
import network
5-
import uwebsocket
6-
import websocket_helper
5+
import os
6+
import socket
7+
import sys
8+
import websocket
79
import _webrepl
810

911
listen_s = None
1012
client_s = None
1113

14+
DEBUG = 0
15+
16+
_DEFAULT_STATIC_HOST = const("https://micropython.org/webrepl/")
17+
static_host = _DEFAULT_STATIC_HOST
18+
19+
20+
def server_handshake(cl):
21+
req = cl.makefile("rwb", 0)
22+
# Skip HTTP GET line.
23+
l = req.readline()
24+
if DEBUG:
25+
sys.stdout.write(repr(l))
26+
27+
webkey = None
28+
upgrade = False
29+
websocket = False
30+
31+
while True:
32+
l = req.readline()
33+
if not l:
34+
# EOF in headers.
35+
return False
36+
if l == b"\r\n":
37+
break
38+
if DEBUG:
39+
sys.stdout.write(l)
40+
h, v = [x.strip() for x in l.split(b":", 1)]
41+
if DEBUG:
42+
print((h, v))
43+
if h == b"Sec-WebSocket-Key":
44+
webkey = v
45+
elif h == b"Connection" and b"Upgrade" in v:
46+
upgrade = True
47+
elif h == b"Upgrade" and v == b"websocket":
48+
websocket = True
49+
50+
if not (upgrade and websocket and webkey):
51+
return False
52+
53+
if DEBUG:
54+
print("Sec-WebSocket-Key:", webkey, len(webkey))
55+
56+
d = hashlib.sha1(webkey)
57+
d.update(b"258EAFA5-E914-47DA-95CA-C5AB0DC85B11")
58+
respkey = d.digest()
59+
respkey = binascii.b2a_base64(respkey)[:-1]
60+
if DEBUG:
61+
print("respkey:", respkey)
62+
63+
cl.send(
64+
b"""\
65+
HTTP/1.1 101 Switching Protocols\r
66+
Upgrade: websocket\r
67+
Connection: Upgrade\r
68+
Sec-WebSocket-Accept: """
69+
)
70+
cl.send(respkey)
71+
cl.send("\r\n\r\n")
72+
73+
return True
74+
75+
76+
def send_html(cl):
77+
cl.send(
78+
b"""\
79+
HTTP/1.0 200 OK\r
80+
\r
81+
<base href=\""""
82+
)
83+
cl.send(static_host)
84+
cl.send(
85+
b"""\"></base>\r
86+
<script src="webrepl_content.js"></script>\r
87+
"""
88+
)
89+
cl.close()
90+
1291

1392
def setup_conn(port, accept_handler):
1493
global listen_s
@@ -25,48 +104,58 @@ def setup_conn(port, accept_handler):
25104
for i in (network.AP_IF, network.STA_IF):
26105
iface = network.WLAN(i)
27106
if iface.active():
28-
print("WebREPL daemon started on ws://%s:%d" % (iface.ifconfig()[0], port))
107+
print("WebREPL server started on http://%s:%d/" % (iface.ifconfig()[0], port))
29108
return listen_s
30109

31110

32111
def accept_conn(listen_sock):
33112
global client_s
34113
cl, remote_addr = listen_sock.accept()
35-
prev = uos.dupterm(None)
36-
uos.dupterm(prev)
114+
115+
if not server_handshake(cl):
116+
send_html(cl)
117+
return False
118+
119+
prev = os.dupterm(None)
120+
os.dupterm(prev)
37121
if prev:
38122
print("\nConcurrent WebREPL connection from", remote_addr, "rejected")
39123
cl.close()
40-
return
124+
return False
41125
print("\nWebREPL connection from:", remote_addr)
42126
client_s = cl
43-
websocket_helper.server_handshake(cl)
44-
ws = uwebsocket.websocket(cl, True)
127+
128+
ws = websocket.websocket(cl, True)
45129
ws = _webrepl._webrepl(ws)
46130
cl.setblocking(False)
47131
# notify REPL on socket incoming data (ESP32/ESP8266-only)
48-
if hasattr(uos, "dupterm_notify"):
49-
cl.setsockopt(socket.SOL_SOCKET, 20, uos.dupterm_notify)
50-
uos.dupterm(ws)
132+
if hasattr(os, "dupterm_notify"):
133+
cl.setsockopt(socket.SOL_SOCKET, 20, os.dupterm_notify)
134+
os.dupterm(ws)
135+
136+
return True
51137

52138

53139
def stop():
54140
global listen_s, client_s
55-
uos.dupterm(None)
141+
os.dupterm(None)
56142
if client_s:
57143
client_s.close()
58144
if listen_s:
59145
listen_s.close()
60146

61147

62148
def start(port=8266, password=None, accept_handler=accept_conn):
149+
global static_host
63150
stop()
64151
webrepl_pass = password
65152
if webrepl_pass is None:
66153
try:
67154
import webrepl_cfg
68155

69156
webrepl_pass = webrepl_cfg.PASS
157+
if hasattr(webrepl_cfg, "BASE"):
158+
static_host = webrepl_cfg.BASE
70159
except:
71160
print("WebREPL is not configured, run 'import webrepl_setup'")
72161

@@ -75,7 +164,9 @@ def start(port=8266, password=None, accept_handler=accept_conn):
75164

76165
if accept_handler is None:
77166
print("Starting webrepl in foreground mode")
78-
accept_conn(s)
167+
# Run accept_conn to serve HTML until we get a websocket connection.
168+
while not accept_conn(s):
169+
pass
79170
elif password is None:
80171
print("Started webrepl in normal mode")
81172
else:

extmod/webrepl/webrepl_setup.py

-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import sys
22

3-
# import uos as os
43
import os
54
import machine
65

extmod/webrepl/websocket_helper.py

-85
This file was deleted.

0 commit comments

Comments
 (0)