@@ -36,14 +36,31 @@ then close the connection. Finally, empty the list since all connections are
36
36
closing anyway and clients will re-connect from the re-loaded page.
37
37
"""
38
38
function update_and_close_viewers! (wss:: Vector{HTTP.WebSockets.WebSocket} )
39
- foreach (wss) do wsi
40
- try
41
- write (wsi, " update" )
42
- close (wsi)
43
- catch
39
+ ws_to_update_and_close = collect (wss)
40
+ empty! (wss)
41
+
42
+ # send update message to all viewers
43
+ @sync for wsi in ws_to_update_and_close
44
+ isopen (wsi. io) && @async begin
45
+ try
46
+ send (wsi, " update" )
47
+ catch
48
+ end
44
49
end
45
50
end
46
- empty! (wss)
51
+
52
+ # force close all viewers (these will be replaced by 'fresh' ones
53
+ # after the reload triggered by the update message)
54
+ @sync for wsi in ws_to_update_and_close
55
+ isopen (wsi. io) && @async begin
56
+ try
57
+ wsi. writeclosed = wsi. readclosed = true
58
+ close (wsi. io)
59
+ catch
60
+ end
61
+ end
62
+ end
63
+
47
64
return nothing
48
65
end
49
66
@@ -331,41 +348,21 @@ function serve_file(
331
348
return resp
332
349
end
333
350
334
- """
335
- ws_upgrade(http::HTTP.Stream)
336
-
337
- Upgrade the HTTP request in the stream to a websocket.
338
- """
339
- function ws_upgrade (http:: HTTP.Stream )
340
- # adapted from HTTP.WebSockets.upgrade; note that here the upgrade will always
341
- # have the right format as it always triggered by after a Response
342
- HTTP. setstatus (http, 101 )
343
- HTTP. setheader (http, " Upgrade" => " websocket" )
344
- HTTP. setheader (http, " Connection" => " Upgrade" )
345
- key = HTTP. header (http, " Sec-WebSocket-Key" )
346
- HTTP. setheader (http, " Sec-WebSocket-Accept" => HTTP. WebSockets. accept_hash (key))
347
- HTTP. startwrite (http)
348
-
349
- io = http. stream
350
- return HTTP. WebSockets. WebSocket (io; server= true )
351
- end
352
-
353
351
354
352
"""
355
353
ws_tracker(ws::HTTP.WebSockets.WebSocket, target::AbstractString)
356
354
357
- Adds the websocket connection to the viewers in the global dictionary `WS_VIEWERS` to the entry
358
- corresponding to the targeted file.
355
+ Adds the websocket connection to the viewers in the global dictionary
356
+ `WS_VIEWERS` to the entry corresponding to the targeted file.
359
357
"""
360
- function ws_tracker (ws:: HTTP.WebSockets.WebSocket , target:: AbstractString )
361
- # add to list of html files being "watched"
362
- # NOTE: this file always exists because the query is generated just after
363
- # serving it
364
- fs_path = get_fs_path (target)
365
-
366
- # if the file is already being viewed, add ws to it (e.g. several tabs)
367
- # otherwise add to dict
368
- if fs_path ∈ keys (WS_VIEWERS)
358
+ function ws_tracker (ws:: HTTP.WebSockets.WebSocket )
359
+ # NOTE: this file always exists because the query is
360
+ # generated just after serving it
361
+ fs_path = get_fs_path (ws. request. target)
362
+
363
+ # add to list of html files being "watched" if the file is already being
364
+ # viewed, add ws to it (e.g. several tabs) otherwise add to dict
365
+ if haskey (WS_VIEWERS, fs_path)
369
366
push! (WS_VIEWERS[fs_path], ws)
370
367
else
371
368
WS_VIEWERS[fs_path] = [ws]
@@ -463,7 +460,7 @@ directory. (See also [`example`](@ref) for an example folder).
463
460
start (fw)
464
461
465
462
# make request handler
466
- req_handler = HTTP. RequestHandlerFunction () do req
463
+ req_handler = HTTP. Handlers . streamhandler () do req
467
464
req = preprocess_request (req)
468
465
serve_file (
469
466
fw, req;
@@ -472,31 +469,19 @@ directory. (See also [`example`](@ref) for an example folder).
472
469
)
473
470
end
474
471
475
- server = Sockets. listen (parse (IPAddr, host), port)
476
- url = " http://$(host == string (Sockets. localhost) ? " localhost" : host) :$port "
472
+ server, port = get_server (host, port, req_handler)
473
+ host_str = ifelse (host == string (Sockets. localhost), " localhost" , host)
474
+ url = " http://$host_str :$port "
477
475
println (
478
476
" ✓ LiveServer listening on $url / ...\n (use CTRL+C to shut down)"
479
477
)
480
- @async HTTP. listen (host, port;
481
- server= server, readtimeout= 0 , reuse_limit= 0 ) do http:: HTTP.Stream
482
- # reuse_limit=0 ensures that there won't be an error if killing and restarting the server.
483
- if HTTP. WebSockets. is_upgrade (http. message)
484
- # upgrade to websocket
485
- ws = ws_upgrade (http)
486
- # add to list of viewers and keep open until written to
487
- ws_tracker (ws, http. message. target)
488
- else
489
- # handle HTTP request
490
- HTTP. handle (req_handler, http)
491
- end
492
- end
493
478
494
479
launch_browser && open_in_default_browser (url)
495
480
# wait until user interrupts the LiveServer (using CTRL+C).
496
481
try
497
482
counter = 1
498
483
while true
499
- if WS_INTERRUPT. x || fw. status == :interrupted
484
+ if WS_INTERRUPT[] || fw. status == :interrupted
500
485
# rethrow the interruption (which may have happened during
501
486
# the websocket handling or during the file watching)
502
487
throw (InterruptException ())
@@ -509,25 +494,57 @@ directory. (See also [`example`](@ref) for an example folder).
509
494
end
510
495
catch err
511
496
if ! isa (err, InterruptException)
497
+ if VERBOSE[]
498
+ @error " serve error" exception= (err, catch_backtrace ())
499
+ end
512
500
throw (err)
513
501
end
514
502
finally
515
503
# cleanup: close everything that might still be alive
516
- VERBOSE[] && println (" \n ⋮ shutting down LiveServer" )
504
+ print (" \n ⋮ shutting down LiveServer… " )
517
505
# stop the filewatcher
518
506
stop (fw)
519
507
# close any remaining websockets
520
- for wss ∈ values (WS_VIEWERS), wsi ∈ wss
521
- close (wsi. io)
508
+ for wss ∈ values (WS_VIEWERS)
509
+ @sync for wsi in wss
510
+ isopen (wsi. io) && @async begin
511
+ try
512
+ wsi. writeclosed = wsi. readclosed = true
513
+ close (wsi. io)
514
+ catch
515
+ end
516
+ end
517
+ end
522
518
end
523
519
# empty the dictionary of viewers
524
520
empty! (WS_VIEWERS)
525
521
# shut down the server
526
- close (server)
527
- VERBOSE[] && println (" \n ✓ LiveServer shut down." )
522
+ HTTP. Servers. forceclose (server)
528
523
# reset environment variables
529
- CONTENT_DIR[] = " "
524
+ CONTENT_DIR[] = " "
530
525
WS_INTERRUPT[] = false
526
+ println (" ✓" )
531
527
end
532
528
return nothing
533
529
end
530
+
531
+ function get_server (host, port, req_handler; incr= 0 )
532
+ if incr >= 10
533
+ @error " couldn't find a free port in $incr tries"
534
+ end
535
+ try
536
+ server = HTTP. listen! (host, port; readtimeout= 0 ) do http:: HTTP.Stream
537
+ if HTTP. WebSockets. isupgrade (http. message)
538
+ # upgrade to websocket and add to list of viewers and keep open
539
+ # until written to
540
+ HTTP. WebSockets. upgrade (ws_tracker, http)
541
+ else
542
+ # handle HTTP request
543
+ return req_handler (http)
544
+ end
545
+ end
546
+ return server, port
547
+ catch IOError
548
+ return get_server (host, port+ 1 , req_handler; incr= incr+ 1 )
549
+ end
550
+ end
0 commit comments