borecli 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- bore/__init__.py +0 -0
- bore/auth.py +104 -0
- bore/cli.py +378 -0
- bore/config.py +132 -0
- bore/frames.py +376 -0
- bore/handlers/__init__.py +0 -0
- bore/handlers/error_pages.py +223 -0
- bore/handlers/http_handler.py +694 -0
- bore/handlers/websocket_handler.py +975 -0
- bore/parse_duration.py +32 -0
- bore/protocol.py +72 -0
- bore/tunnel/__init__.py +0 -0
- bore/tunnel/client.py +311 -0
- bore/tunnel/dispatcher.py +277 -0
- bore/tunnel/heartbeat.py +154 -0
- bore/tunnel.py +43 -0
- bore/websocket/__init__.py +0 -0
- bore/websocket/connection.py +108 -0
- bore/websocket/frames.py +162 -0
- bore/websocket/manager.py +174 -0
- bore/websocket/proxy.py +147 -0
- bore/websocket_client.py +338 -0
- borecli-1.0.0.dist-info/METADATA +231 -0
- borecli-1.0.0.dist-info/RECORD +28 -0
- borecli-1.0.0.dist-info/WHEEL +5 -0
- borecli-1.0.0.dist-info/entry_points.txt +2 -0
- borecli-1.0.0.dist-info/licenses/LICENSE +0 -0
- borecli-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,975 @@
|
|
|
1
|
+
|
|
2
|
+
# borecli/bore/handlers/websocket_handler.py
|
|
3
|
+
|
|
4
|
+
import asyncio
|
|
5
|
+
import base64
|
|
6
|
+
import logging
|
|
7
|
+
import click
|
|
8
|
+
import websockets
|
|
9
|
+
from websockets.exceptions import ConnectionClosed
|
|
10
|
+
|
|
11
|
+
from bore.frames import (
|
|
12
|
+
make_frame,
|
|
13
|
+
make_ws_message,
|
|
14
|
+
make_ws_close,
|
|
15
|
+
send_frame,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
from bore.protocol import MessageType
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
#
|
|
25
|
+
# Prevent concurrent websocket frame corruption
|
|
26
|
+
#
|
|
27
|
+
SEND_LOCK = asyncio.Lock()
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class WebSocketConnectionManager:
|
|
31
|
+
"""
|
|
32
|
+
Maintains active localhost websocket connections.
|
|
33
|
+
|
|
34
|
+
connection_id:
|
|
35
|
+
BoreHook websocket identifier
|
|
36
|
+
|
|
37
|
+
websocket:
|
|
38
|
+
Local application websocket
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
def __init__(self):
|
|
42
|
+
|
|
43
|
+
self._connections = {}
|
|
44
|
+
|
|
45
|
+
self._relay_tasks = {}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def get(
|
|
49
|
+
self,
|
|
50
|
+
connection_id,
|
|
51
|
+
):
|
|
52
|
+
|
|
53
|
+
return self._connections.get(
|
|
54
|
+
connection_id
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def exists(
|
|
59
|
+
self,
|
|
60
|
+
connection_id,
|
|
61
|
+
):
|
|
62
|
+
|
|
63
|
+
return connection_id in self._connections
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def register(
|
|
67
|
+
self,
|
|
68
|
+
connection_id,
|
|
69
|
+
websocket,
|
|
70
|
+
):
|
|
71
|
+
|
|
72
|
+
self._connections[
|
|
73
|
+
connection_id
|
|
74
|
+
] = websocket
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def register_task(
|
|
78
|
+
self,
|
|
79
|
+
connection_id,
|
|
80
|
+
task,
|
|
81
|
+
):
|
|
82
|
+
|
|
83
|
+
self._relay_tasks[
|
|
84
|
+
connection_id
|
|
85
|
+
] = task
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
async def remove(
|
|
89
|
+
self,
|
|
90
|
+
connection_id,
|
|
91
|
+
):
|
|
92
|
+
|
|
93
|
+
#
|
|
94
|
+
# Stop relay task
|
|
95
|
+
#
|
|
96
|
+
|
|
97
|
+
task = self._relay_tasks.pop(
|
|
98
|
+
connection_id,
|
|
99
|
+
None,
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
if (
|
|
104
|
+
task
|
|
105
|
+
and task != asyncio.current_task()
|
|
106
|
+
):
|
|
107
|
+
|
|
108
|
+
task.cancel()
|
|
109
|
+
|
|
110
|
+
try:
|
|
111
|
+
|
|
112
|
+
await task
|
|
113
|
+
|
|
114
|
+
except asyncio.CancelledError:
|
|
115
|
+
|
|
116
|
+
pass
|
|
117
|
+
|
|
118
|
+
except Exception:
|
|
119
|
+
|
|
120
|
+
logger.debug(
|
|
121
|
+
"Relay shutdown failed.",exc_info=True
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
#
|
|
127
|
+
# Close local websocket
|
|
128
|
+
#
|
|
129
|
+
|
|
130
|
+
websocket = self._connections.pop(
|
|
131
|
+
connection_id,
|
|
132
|
+
None,
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
if websocket:
|
|
137
|
+
|
|
138
|
+
try:
|
|
139
|
+
|
|
140
|
+
await websocket.close()
|
|
141
|
+
|
|
142
|
+
except Exception:
|
|
143
|
+
|
|
144
|
+
pass
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
async def shutdown(self):
|
|
149
|
+
|
|
150
|
+
connections = list(
|
|
151
|
+
self._connections.keys()
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
for connection_id in connections:
|
|
155
|
+
|
|
156
|
+
await self.remove(
|
|
157
|
+
connection_id
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
#
|
|
162
|
+
# Global websocket manager
|
|
163
|
+
#
|
|
164
|
+
|
|
165
|
+
manager = WebSocketConnectionManager()
|
|
166
|
+
|
|
167
|
+
# ------------------------------------------------------------
|
|
168
|
+
# Connect localhost websocket
|
|
169
|
+
# ------------------------------------------------------------
|
|
170
|
+
|
|
171
|
+
async def handle_ws_connect(
|
|
172
|
+
*,
|
|
173
|
+
websocket,
|
|
174
|
+
local_port,
|
|
175
|
+
frame,
|
|
176
|
+
):
|
|
177
|
+
"""
|
|
178
|
+
Handle ws.connect frame from BoreHook server.
|
|
179
|
+
|
|
180
|
+
Creates a websocket connection to the user's
|
|
181
|
+
local websocket application.
|
|
182
|
+
"""
|
|
183
|
+
|
|
184
|
+
connection_id = frame.get(
|
|
185
|
+
"connection_id"
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
path = frame.get(
|
|
189
|
+
"path",
|
|
190
|
+
"/",
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
query = frame.get(
|
|
194
|
+
"query",
|
|
195
|
+
"",
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
if not connection_id or not local_port:
|
|
200
|
+
|
|
201
|
+
logger.error(
|
|
202
|
+
"Invalid ws.connect frame"
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
return
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
#
|
|
209
|
+
# Prevent duplicate connections
|
|
210
|
+
#
|
|
211
|
+
|
|
212
|
+
if manager.exists(connection_id):
|
|
213
|
+
|
|
214
|
+
logger.warning(
|
|
215
|
+
"Websocket already exists: %s",
|
|
216
|
+
connection_id,
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
return
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
#
|
|
224
|
+
# Build localhost websocket URL
|
|
225
|
+
#
|
|
226
|
+
|
|
227
|
+
url = (
|
|
228
|
+
f"ws://127.0.0.1:"
|
|
229
|
+
f"{local_port}"
|
|
230
|
+
f"{path}"
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
if query:
|
|
235
|
+
|
|
236
|
+
url += f"?{query}"
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
#
|
|
241
|
+
# Copy safe headers
|
|
242
|
+
#
|
|
243
|
+
|
|
244
|
+
incoming_headers = frame.get(
|
|
245
|
+
"headers",
|
|
246
|
+
{},
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
excluded_headers = {
|
|
251
|
+
|
|
252
|
+
"host",
|
|
253
|
+
"connection",
|
|
254
|
+
"upgrade",
|
|
255
|
+
"origin",
|
|
256
|
+
"sec-websocket-key",
|
|
257
|
+
"sec-websocket-version",
|
|
258
|
+
"sec-websocket-extensions",
|
|
259
|
+
"sec-websocket-protocol",
|
|
260
|
+
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
headers = [
|
|
266
|
+
|
|
267
|
+
(
|
|
268
|
+
key,
|
|
269
|
+
value,
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
for key, value in incoming_headers.items()
|
|
273
|
+
|
|
274
|
+
if key.lower()
|
|
275
|
+
not in excluded_headers
|
|
276
|
+
|
|
277
|
+
]
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
#
|
|
282
|
+
# Rewrite local headers
|
|
283
|
+
#
|
|
284
|
+
|
|
285
|
+
headers.append(
|
|
286
|
+
(
|
|
287
|
+
"Host",
|
|
288
|
+
f"127.0.0.1:{local_port}",
|
|
289
|
+
)
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
headers.append(
|
|
294
|
+
(
|
|
295
|
+
"Origin",
|
|
296
|
+
f"http://127.0.0.1:{local_port}",
|
|
297
|
+
)
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
logger.info(
|
|
303
|
+
"Connecting local websocket %s",
|
|
304
|
+
url,
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
#
|
|
310
|
+
# Connect to local websocket server
|
|
311
|
+
#
|
|
312
|
+
|
|
313
|
+
try:
|
|
314
|
+
|
|
315
|
+
local_ws = await websockets.connect(
|
|
316
|
+
|
|
317
|
+
url,
|
|
318
|
+
|
|
319
|
+
additional_headers=headers,
|
|
320
|
+
|
|
321
|
+
ping_interval=None,
|
|
322
|
+
|
|
323
|
+
max_size=None,
|
|
324
|
+
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
except Exception:
|
|
329
|
+
|
|
330
|
+
logger.debug("Local websocket connection failed.", exc_info=True)
|
|
331
|
+
|
|
332
|
+
click.secho(
|
|
333
|
+
f"⚠️ Request received, but nothing is listening on "
|
|
334
|
+
f"127.0.0.1:{local_port} — is your local app running?",
|
|
335
|
+
fg="yellow",
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
async with SEND_LOCK:
|
|
340
|
+
|
|
341
|
+
await send_frame(
|
|
342
|
+
|
|
343
|
+
websocket,
|
|
344
|
+
|
|
345
|
+
make_ws_close(
|
|
346
|
+
|
|
347
|
+
connection_id=connection_id,
|
|
348
|
+
|
|
349
|
+
code=1011,
|
|
350
|
+
|
|
351
|
+
reason=
|
|
352
|
+
"Unable to connect localhost websocket",
|
|
353
|
+
|
|
354
|
+
),
|
|
355
|
+
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
return
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
#
|
|
364
|
+
# Register connection
|
|
365
|
+
#
|
|
366
|
+
|
|
367
|
+
manager.register(
|
|
368
|
+
|
|
369
|
+
connection_id,
|
|
370
|
+
|
|
371
|
+
local_ws,
|
|
372
|
+
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
#
|
|
378
|
+
# Tell server websocket is ready
|
|
379
|
+
#
|
|
380
|
+
|
|
381
|
+
async with SEND_LOCK:
|
|
382
|
+
|
|
383
|
+
await send_frame(
|
|
384
|
+
|
|
385
|
+
websocket,
|
|
386
|
+
|
|
387
|
+
make_frame(
|
|
388
|
+
|
|
389
|
+
MessageType.WS_CONNECTED,
|
|
390
|
+
|
|
391
|
+
connection_id=connection_id,
|
|
392
|
+
|
|
393
|
+
),
|
|
394
|
+
|
|
395
|
+
)
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
#
|
|
400
|
+
# Start localhost -> BoreHook relay
|
|
401
|
+
#
|
|
402
|
+
|
|
403
|
+
relay_task = asyncio.create_task(
|
|
404
|
+
|
|
405
|
+
relay_local_to_server(
|
|
406
|
+
|
|
407
|
+
websocket=websocket,
|
|
408
|
+
|
|
409
|
+
connection_id=connection_id,
|
|
410
|
+
|
|
411
|
+
local_ws=local_ws,
|
|
412
|
+
|
|
413
|
+
)
|
|
414
|
+
|
|
415
|
+
)
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
manager.register_task(
|
|
419
|
+
|
|
420
|
+
connection_id,
|
|
421
|
+
|
|
422
|
+
relay_task,
|
|
423
|
+
|
|
424
|
+
)
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
logger.info(
|
|
428
|
+
|
|
429
|
+
"Websocket connected: %s",
|
|
430
|
+
|
|
431
|
+
connection_id,
|
|
432
|
+
|
|
433
|
+
)
|
|
434
|
+
|
|
435
|
+
# ------------------------------------------------------------
|
|
436
|
+
# Relay localhost websocket -> BoreHook server
|
|
437
|
+
# ------------------------------------------------------------
|
|
438
|
+
|
|
439
|
+
async def relay_local_to_server(
|
|
440
|
+
*,
|
|
441
|
+
websocket,
|
|
442
|
+
connection_id,
|
|
443
|
+
local_ws,
|
|
444
|
+
):
|
|
445
|
+
"""
|
|
446
|
+
Forward websocket messages from the user's
|
|
447
|
+
localhost application back to BoreHook.
|
|
448
|
+
|
|
449
|
+
Direction:
|
|
450
|
+
|
|
451
|
+
localhost websocket
|
|
452
|
+
|
|
|
453
|
+
|
|
|
454
|
+
v
|
|
455
|
+
|
|
456
|
+
BoreHook websocket
|
|
457
|
+
"""
|
|
458
|
+
|
|
459
|
+
logger.info(
|
|
460
|
+
"Starting websocket relay: %s",
|
|
461
|
+
connection_id,
|
|
462
|
+
)
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
try:
|
|
466
|
+
|
|
467
|
+
async for message in local_ws:
|
|
468
|
+
|
|
469
|
+
|
|
470
|
+
#
|
|
471
|
+
# Binary websocket message
|
|
472
|
+
#
|
|
473
|
+
|
|
474
|
+
if isinstance(
|
|
475
|
+
message,
|
|
476
|
+
bytes,
|
|
477
|
+
):
|
|
478
|
+
|
|
479
|
+
|
|
480
|
+
payload = (
|
|
481
|
+
base64.b64encode(
|
|
482
|
+
message
|
|
483
|
+
)
|
|
484
|
+
.decode("ascii")
|
|
485
|
+
)
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
frame = make_ws_message(
|
|
489
|
+
|
|
490
|
+
connection_id=connection_id,
|
|
491
|
+
|
|
492
|
+
binary=True,
|
|
493
|
+
|
|
494
|
+
body=payload,
|
|
495
|
+
|
|
496
|
+
)
|
|
497
|
+
|
|
498
|
+
|
|
499
|
+
#
|
|
500
|
+
# Text websocket message
|
|
501
|
+
#
|
|
502
|
+
|
|
503
|
+
else:
|
|
504
|
+
|
|
505
|
+
|
|
506
|
+
frame = make_ws_message(
|
|
507
|
+
|
|
508
|
+
connection_id=connection_id,
|
|
509
|
+
|
|
510
|
+
binary=False,
|
|
511
|
+
|
|
512
|
+
body=message,
|
|
513
|
+
|
|
514
|
+
)
|
|
515
|
+
|
|
516
|
+
|
|
517
|
+
|
|
518
|
+
#
|
|
519
|
+
# Send safely
|
|
520
|
+
#
|
|
521
|
+
|
|
522
|
+
async with SEND_LOCK:
|
|
523
|
+
|
|
524
|
+
await send_frame(
|
|
525
|
+
|
|
526
|
+
websocket,
|
|
527
|
+
|
|
528
|
+
frame,
|
|
529
|
+
|
|
530
|
+
)
|
|
531
|
+
|
|
532
|
+
|
|
533
|
+
|
|
534
|
+
except ConnectionClosed as exc:
|
|
535
|
+
|
|
536
|
+
|
|
537
|
+
logger.info(
|
|
538
|
+
|
|
539
|
+
"Local websocket closed %s (%s)",
|
|
540
|
+
|
|
541
|
+
connection_id,
|
|
542
|
+
|
|
543
|
+
exc.code,
|
|
544
|
+
|
|
545
|
+
)
|
|
546
|
+
|
|
547
|
+
|
|
548
|
+
async with SEND_LOCK:
|
|
549
|
+
|
|
550
|
+
await send_frame(
|
|
551
|
+
|
|
552
|
+
websocket,
|
|
553
|
+
|
|
554
|
+
make_ws_close(
|
|
555
|
+
|
|
556
|
+
connection_id=connection_id,
|
|
557
|
+
|
|
558
|
+
code=exc.code,
|
|
559
|
+
|
|
560
|
+
reason=(
|
|
561
|
+
exc.reason
|
|
562
|
+
or ""
|
|
563
|
+
),
|
|
564
|
+
|
|
565
|
+
),
|
|
566
|
+
|
|
567
|
+
)
|
|
568
|
+
|
|
569
|
+
|
|
570
|
+
|
|
571
|
+
except asyncio.CancelledError:
|
|
572
|
+
|
|
573
|
+
|
|
574
|
+
logger.info(
|
|
575
|
+
|
|
576
|
+
"Relay cancelled: %s",
|
|
577
|
+
|
|
578
|
+
connection_id,
|
|
579
|
+
|
|
580
|
+
)
|
|
581
|
+
|
|
582
|
+
raise
|
|
583
|
+
|
|
584
|
+
|
|
585
|
+
|
|
586
|
+
except Exception:
|
|
587
|
+
logger.debug("Websocket relay failed.", exc_info=True)
|
|
588
|
+
|
|
589
|
+
click.secho(
|
|
590
|
+
f"⚠️ Websocket relay failed:{connection_id}",
|
|
591
|
+
fg="yellow",
|
|
592
|
+
)
|
|
593
|
+
|
|
594
|
+
|
|
595
|
+
try:
|
|
596
|
+
|
|
597
|
+
async with SEND_LOCK:
|
|
598
|
+
|
|
599
|
+
await send_frame(
|
|
600
|
+
|
|
601
|
+
websocket,
|
|
602
|
+
|
|
603
|
+
make_ws_close(
|
|
604
|
+
|
|
605
|
+
connection_id=connection_id,
|
|
606
|
+
|
|
607
|
+
code=1011,
|
|
608
|
+
|
|
609
|
+
reason="Relay failure",
|
|
610
|
+
|
|
611
|
+
),
|
|
612
|
+
|
|
613
|
+
)
|
|
614
|
+
|
|
615
|
+
except Exception:
|
|
616
|
+
|
|
617
|
+
pass
|
|
618
|
+
|
|
619
|
+
|
|
620
|
+
|
|
621
|
+
finally:
|
|
622
|
+
|
|
623
|
+
|
|
624
|
+
await manager.remove(
|
|
625
|
+
|
|
626
|
+
connection_id
|
|
627
|
+
|
|
628
|
+
)
|
|
629
|
+
|
|
630
|
+
|
|
631
|
+
logger.info(
|
|
632
|
+
|
|
633
|
+
"Relay stopped: %s",
|
|
634
|
+
|
|
635
|
+
connection_id,
|
|
636
|
+
|
|
637
|
+
)
|
|
638
|
+
# ------------------------------------------------------------
|
|
639
|
+
# Relay BoreHook server -> localhost websocket
|
|
640
|
+
# ------------------------------------------------------------
|
|
641
|
+
|
|
642
|
+
async def handle_ws_message(
|
|
643
|
+
*,
|
|
644
|
+
frame,
|
|
645
|
+
):
|
|
646
|
+
"""
|
|
647
|
+
Forward a websocket message received from
|
|
648
|
+
BoreHook to the user's local websocket.
|
|
649
|
+
|
|
650
|
+
Direction:
|
|
651
|
+
|
|
652
|
+
BoreHook websocket
|
|
653
|
+
|
|
|
654
|
+
|
|
|
655
|
+
v
|
|
656
|
+
|
|
657
|
+
localhost websocket
|
|
658
|
+
"""
|
|
659
|
+
|
|
660
|
+
connection_id = frame.get(
|
|
661
|
+
"connection_id"
|
|
662
|
+
)
|
|
663
|
+
|
|
664
|
+
|
|
665
|
+
if not connection_id:
|
|
666
|
+
|
|
667
|
+
logger.warning(
|
|
668
|
+
"ws.message missing connection_id"
|
|
669
|
+
)
|
|
670
|
+
|
|
671
|
+
return
|
|
672
|
+
|
|
673
|
+
|
|
674
|
+
|
|
675
|
+
#
|
|
676
|
+
# Find local websocket
|
|
677
|
+
#
|
|
678
|
+
|
|
679
|
+
local_ws = manager.get(
|
|
680
|
+
connection_id
|
|
681
|
+
)
|
|
682
|
+
|
|
683
|
+
|
|
684
|
+
if local_ws is None:
|
|
685
|
+
|
|
686
|
+
logger.warning(
|
|
687
|
+
|
|
688
|
+
"Unknown websocket connection: %s",
|
|
689
|
+
|
|
690
|
+
connection_id,
|
|
691
|
+
|
|
692
|
+
)
|
|
693
|
+
|
|
694
|
+
return
|
|
695
|
+
|
|
696
|
+
|
|
697
|
+
|
|
698
|
+
binary = frame.get(
|
|
699
|
+
"binary",
|
|
700
|
+
False,
|
|
701
|
+
)
|
|
702
|
+
|
|
703
|
+
|
|
704
|
+
body = frame.get(
|
|
705
|
+
"body",
|
|
706
|
+
"",
|
|
707
|
+
)
|
|
708
|
+
|
|
709
|
+
|
|
710
|
+
|
|
711
|
+
try:
|
|
712
|
+
|
|
713
|
+
|
|
714
|
+
#
|
|
715
|
+
# Binary websocket frame
|
|
716
|
+
#
|
|
717
|
+
|
|
718
|
+
if binary:
|
|
719
|
+
|
|
720
|
+
|
|
721
|
+
payload = base64.b64decode(
|
|
722
|
+
body
|
|
723
|
+
)
|
|
724
|
+
|
|
725
|
+
|
|
726
|
+
await local_ws.send(
|
|
727
|
+
payload
|
|
728
|
+
)
|
|
729
|
+
|
|
730
|
+
|
|
731
|
+
|
|
732
|
+
#
|
|
733
|
+
# Text websocket frame
|
|
734
|
+
#
|
|
735
|
+
|
|
736
|
+
else:
|
|
737
|
+
|
|
738
|
+
|
|
739
|
+
await local_ws.send(
|
|
740
|
+
body
|
|
741
|
+
)
|
|
742
|
+
|
|
743
|
+
|
|
744
|
+
|
|
745
|
+
logger.debug(
|
|
746
|
+
|
|
747
|
+
"Forwarded websocket message: %s",
|
|
748
|
+
|
|
749
|
+
connection_id,
|
|
750
|
+
|
|
751
|
+
)
|
|
752
|
+
|
|
753
|
+
|
|
754
|
+
|
|
755
|
+
except ConnectionClosed:
|
|
756
|
+
|
|
757
|
+
|
|
758
|
+
logger.info(
|
|
759
|
+
|
|
760
|
+
"Local websocket closed: %s",
|
|
761
|
+
|
|
762
|
+
connection_id,
|
|
763
|
+
|
|
764
|
+
)
|
|
765
|
+
|
|
766
|
+
|
|
767
|
+
await manager.remove(
|
|
768
|
+
|
|
769
|
+
connection_id
|
|
770
|
+
|
|
771
|
+
)
|
|
772
|
+
|
|
773
|
+
|
|
774
|
+
|
|
775
|
+
except Exception:
|
|
776
|
+
logger.debug("Unable to forward websocket message.", exc_info=True)
|
|
777
|
+
|
|
778
|
+
click.secho(
|
|
779
|
+
f"⚠️ Unable to forward websocket message",
|
|
780
|
+
fg="yellow",
|
|
781
|
+
)
|
|
782
|
+
|
|
783
|
+
|
|
784
|
+
await manager.remove(
|
|
785
|
+
|
|
786
|
+
connection_id
|
|
787
|
+
|
|
788
|
+
)
|
|
789
|
+
|
|
790
|
+
# ------------------------------------------------------------
|
|
791
|
+
# Close localhost websocket
|
|
792
|
+
# ------------------------------------------------------------
|
|
793
|
+
|
|
794
|
+
async def handle_ws_close(
|
|
795
|
+
*,
|
|
796
|
+
frame,
|
|
797
|
+
):
|
|
798
|
+
"""
|
|
799
|
+
Handle ws.close frame from BoreHook server.
|
|
800
|
+
|
|
801
|
+
Direction:
|
|
802
|
+
|
|
803
|
+
BoreHook
|
|
804
|
+
|
|
|
805
|
+
|
|
|
806
|
+
v
|
|
807
|
+
|
|
808
|
+
localhost websocket close
|
|
809
|
+
"""
|
|
810
|
+
|
|
811
|
+
connection_id = frame.get(
|
|
812
|
+
"connection_id"
|
|
813
|
+
)
|
|
814
|
+
|
|
815
|
+
|
|
816
|
+
if not connection_id:
|
|
817
|
+
|
|
818
|
+
logger.warning(
|
|
819
|
+
"ws.close missing connection_id"
|
|
820
|
+
)
|
|
821
|
+
|
|
822
|
+
return
|
|
823
|
+
|
|
824
|
+
|
|
825
|
+
|
|
826
|
+
code = frame.get(
|
|
827
|
+
"code",
|
|
828
|
+
1000,
|
|
829
|
+
)
|
|
830
|
+
|
|
831
|
+
|
|
832
|
+
reason = frame.get(
|
|
833
|
+
"reason",
|
|
834
|
+
"",
|
|
835
|
+
)
|
|
836
|
+
|
|
837
|
+
|
|
838
|
+
|
|
839
|
+
local_ws = manager.get(
|
|
840
|
+
connection_id
|
|
841
|
+
)
|
|
842
|
+
|
|
843
|
+
|
|
844
|
+
#
|
|
845
|
+
# Already removed
|
|
846
|
+
#
|
|
847
|
+
|
|
848
|
+
if local_ws is None:
|
|
849
|
+
|
|
850
|
+
|
|
851
|
+
logger.debug(
|
|
852
|
+
|
|
853
|
+
"Websocket already closed: %s",
|
|
854
|
+
|
|
855
|
+
connection_id,
|
|
856
|
+
|
|
857
|
+
)
|
|
858
|
+
|
|
859
|
+
return
|
|
860
|
+
|
|
861
|
+
|
|
862
|
+
|
|
863
|
+
logger.info(
|
|
864
|
+
|
|
865
|
+
"Closing websocket %s (%s)",
|
|
866
|
+
|
|
867
|
+
connection_id,
|
|
868
|
+
|
|
869
|
+
code,
|
|
870
|
+
|
|
871
|
+
)
|
|
872
|
+
|
|
873
|
+
|
|
874
|
+
|
|
875
|
+
try:
|
|
876
|
+
|
|
877
|
+
|
|
878
|
+
await local_ws.close(
|
|
879
|
+
|
|
880
|
+
code=code,
|
|
881
|
+
|
|
882
|
+
reason=reason,
|
|
883
|
+
|
|
884
|
+
)
|
|
885
|
+
|
|
886
|
+
|
|
887
|
+
|
|
888
|
+
except ConnectionClosed:
|
|
889
|
+
|
|
890
|
+
|
|
891
|
+
pass
|
|
892
|
+
|
|
893
|
+
|
|
894
|
+
|
|
895
|
+
except Exception:
|
|
896
|
+
logger.debug("Failed closing local websocket.", exc_info=True)
|
|
897
|
+
|
|
898
|
+
click.secho(
|
|
899
|
+
f"⚠️ Request received, but "
|
|
900
|
+
f"Failed closing local websocket",
|
|
901
|
+
fg="yellow",
|
|
902
|
+
)
|
|
903
|
+
|
|
904
|
+
|
|
905
|
+
|
|
906
|
+
finally:
|
|
907
|
+
|
|
908
|
+
|
|
909
|
+
await manager.remove(
|
|
910
|
+
|
|
911
|
+
connection_id
|
|
912
|
+
|
|
913
|
+
)
|
|
914
|
+
|
|
915
|
+
|
|
916
|
+
logger.info(
|
|
917
|
+
|
|
918
|
+
"Websocket removed: %s",
|
|
919
|
+
|
|
920
|
+
connection_id,
|
|
921
|
+
|
|
922
|
+
)
|
|
923
|
+
|
|
924
|
+
# ------------------------------------------------------------
|
|
925
|
+
# Shutdown
|
|
926
|
+
# ------------------------------------------------------------
|
|
927
|
+
|
|
928
|
+
async def shutdown():
|
|
929
|
+
"""
|
|
930
|
+
Close every active websocket connection.
|
|
931
|
+
|
|
932
|
+
Called when TunnelClient stops.
|
|
933
|
+
"""
|
|
934
|
+
|
|
935
|
+
logger.info(
|
|
936
|
+
"Shutting down websocket handler..."
|
|
937
|
+
)
|
|
938
|
+
|
|
939
|
+
|
|
940
|
+
try:
|
|
941
|
+
|
|
942
|
+
await manager.shutdown()
|
|
943
|
+
|
|
944
|
+
|
|
945
|
+
logger.info(
|
|
946
|
+
"All websocket connections closed."
|
|
947
|
+
)
|
|
948
|
+
|
|
949
|
+
|
|
950
|
+
except Exception:
|
|
951
|
+
|
|
952
|
+
logger.debug("Websocket shutdown failed.", exc_info=True)
|
|
953
|
+
|
|
954
|
+
click.secho(
|
|
955
|
+
f"⚠️ Websocket shutdown failed",
|
|
956
|
+
fg="yellow",
|
|
957
|
+
)
|
|
958
|
+
|
|
959
|
+
|
|
960
|
+
|
|
961
|
+
# ------------------------------------------------------------
|
|
962
|
+
# Public exports
|
|
963
|
+
# ------------------------------------------------------------
|
|
964
|
+
|
|
965
|
+
__all__ = [
|
|
966
|
+
|
|
967
|
+
"handle_ws_connect",
|
|
968
|
+
|
|
969
|
+
"handle_ws_message",
|
|
970
|
+
|
|
971
|
+
"handle_ws_close",
|
|
972
|
+
|
|
973
|
+
"shutdown",
|
|
974
|
+
|
|
975
|
+
]
|