Skip to content

Commit ded962e

Browse files
authored
bringing in http1 changes (#146)
* bringing in http1 changes (v2)
1 parent 1bc0dc1 commit ded962e

File tree

8 files changed

+147
-129
lines changed

8 files changed

+147
-129
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ jobs:
1515
fail-fast: false
1616
matrix:
1717
version:
18-
- '1.3'
18+
- '1.6'
1919
- '1'
2020
os:
2121
- ubuntu-latest

Project.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "LiveServer"
22
uuid = "16fef848-5104-11e9-1b77-fb7a48bbb589"
33
authors = ["Jonas Asprion <jonas.asprion@gmx.ch", "Thibaut Lienart <tlienart@me.com>"]
4-
version = "0.9.2"
4+
version = "1.0.0"
55

66
[deps]
77
Crayons = "a8cc5b0e-0ffa-5ad4-8c14-923d3ee1735f"
@@ -14,6 +14,6 @@ Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
1414

1515
[compat]
1616
Crayons = "4"
17-
HTTP = "0.8, 0.9"
17+
HTTP = "1"
1818
MIMEs = "0.1"
19-
julia = "1.3"
19+
julia = "1.6"

src/LiveServer.jl

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -26,18 +26,6 @@ const WS_VIEWERS = Dict{String,Vector{HTTP.WebSockets.WebSocket}}()
2626
"""Keep track of whether an interruption happened while processing a websocket."""
2727
const WS_INTERRUPT = Base.Ref{Bool}(false)
2828

29-
# to fix lots of false error messages from HTTP
30-
# https://github.com/JuliaWeb/HTTP.jl/pull/546
31-
# we do HTTP.Stream{HTTP.Messages.Request,S} instead of just HTTP.Stream to prevent the Julia warning about incremental compilation
32-
# This hack was kindly suggested by Fons van der Plas, the author of Pluto.jl see
33-
# https://github.com/fonsp/Pluto.jl/commit/34d41e63138ee6dad178cd9916d4721441eaf710
34-
function HTTP.closebody(http::HTTP.Stream{HTTP.Messages.Request,S}) where S <: IO
35-
http.writechunked || return
36-
http.writechunked = false
37-
try; write(http.stream, "0\r\n\r\n"); catch; end
38-
return
39-
end
40-
4129
#
4230
# Functions
4331
#

src/client.html

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
<!-- browser-reload script, automatically added by the LiveServer.jl -->
22
<script>
3-
var ws_liveserver_M3sp9 = new WebSocket((location.protocol=="https:" ? "wss:" : "ws:") + "//" + location.host + location.pathname);
3+
var ws_liveserver_M3sp9 = new WebSocket(
4+
(location.protocol=="https:" ? "wss:" : "ws:") + "//" + location.host + location.pathname
5+
);
46
ws_liveserver_M3sp9.onmessage = function(msg) {
57
if (msg.data === "update") {
68
ws_liveserver_M3sp9.close();

src/file_watching.jl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ has not changed, and 1 if it has changed.
2626
function has_changed(wf::WatchedFile)
2727
if !isfile(wf.path)
2828
# isfile may return false for a file
29-
# currently being written. Wait for 0.1s
29+
# currently being written. Wait for 0.1s
3030
# then retry once more:
3131
sleep(0.1)
3232
isfile(wf.path) || return -1
@@ -111,8 +111,8 @@ function file_watcher_task!(fw::FileWatcher)
111111
catch err
112112
fw.status = :interrupted
113113
# an InterruptException is the normal way for this task to end
114-
if !isa(err, InterruptException)
115-
error("An error happened whilst watching files; shutting down. Error was: $err")
114+
if !isa(err, InterruptException) && VERBOSE[]
115+
@error "fw error" exception=(err, catch_backtrace())
116116
end
117117
return nothing
118118
end

src/server.jl

Lines changed: 76 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,31 @@ then close the connection. Finally, empty the list since all connections are
3636
closing anyway and clients will re-connect from the re-loaded page.
3737
"""
3838
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
4449
end
4550
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+
4764
return nothing
4865
end
4966

@@ -331,41 +348,21 @@ function serve_file(
331348
return resp
332349
end
333350

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-
353351

354352
"""
355353
ws_tracker(ws::HTTP.WebSockets.WebSocket, target::AbstractString)
356354
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.
359357
"""
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)
369366
push!(WS_VIEWERS[fs_path], ws)
370367
else
371368
WS_VIEWERS[fs_path] = [ws]
@@ -463,7 +460,7 @@ directory. (See also [`example`](@ref) for an example folder).
463460
start(fw)
464461

465462
# make request handler
466-
req_handler = HTTP.RequestHandlerFunction() do req
463+
req_handler = HTTP.Handlers.streamhandler() do req
467464
req = preprocess_request(req)
468465
serve_file(
469466
fw, req;
@@ -472,31 +469,19 @@ directory. (See also [`example`](@ref) for an example folder).
472469
)
473470
end
474471

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"
477475
println(
478476
"✓ LiveServer listening on $url/ ...\n (use CTRL+C to shut down)"
479477
)
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
493478

494479
launch_browser && open_in_default_browser(url)
495480
# wait until user interrupts the LiveServer (using CTRL+C).
496481
try
497482
counter = 1
498483
while true
499-
if WS_INTERRUPT.x || fw.status == :interrupted
484+
if WS_INTERRUPT[] || fw.status == :interrupted
500485
# rethrow the interruption (which may have happened during
501486
# the websocket handling or during the file watching)
502487
throw(InterruptException())
@@ -509,25 +494,57 @@ directory. (See also [`example`](@ref) for an example folder).
509494
end
510495
catch err
511496
if !isa(err, InterruptException)
497+
if VERBOSE[]
498+
@error "serve error" exception=(err, catch_backtrace())
499+
end
512500
throw(err)
513501
end
514502
finally
515503
# cleanup: close everything that might still be alive
516-
VERBOSE[] && println("\n⋮ shutting down LiveServer")
504+
print("\n⋮ shutting down LiveServer")
517505
# stop the filewatcher
518506
stop(fw)
519507
# 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
522518
end
523519
# empty the dictionary of viewers
524520
empty!(WS_VIEWERS)
525521
# shut down the server
526-
close(server)
527-
VERBOSE[] && println("\n✓ LiveServer shut down.")
522+
HTTP.Servers.forceclose(server)
528523
# reset environment variables
529-
CONTENT_DIR[] = ""
524+
CONTENT_DIR[] = ""
530525
WS_INTERRUPT[] = false
526+
println("")
531527
end
532528
return nothing
533529
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

src/utils.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,7 @@ end
252252
253253
Set the verbosity of LiveServer to either `true` (showing messages upon events) or `false` (default).
254254
"""
255-
setverbose(b::Bool) = (VERBOSE.x = b)
255+
setverbose(b::Bool) = (VERBOSE[] = b)
256256

257257

258258
"""

0 commit comments

Comments
 (0)