Skip to content

Commit f3d1e40

Browse files
committed
Move to HTTP.jl v2
This required making Julia 1.10 the minimum supported version.
1 parent dff5c63 commit f3d1e40

8 files changed

Lines changed: 56 additions & 96 deletions

File tree

.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.6'
18+
- '1.10'
1919
- '1'
2020
os:
2121
- ubuntu-latest

.github/workflows/documentation.yml

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,12 @@ jobs:
1414
runs-on: ubuntu-latest
1515
steps:
1616
- uses: actions/checkout@v6
17-
- uses: julia-actions/setup-julia@v2
17+
- uses: julia-actions/setup-julia@v3
1818
with:
19-
version: '1.9'
20-
- uses: julia-actions/cache@v2
21-
- name: Install dependencies
22-
run: julia --project=docs/ -e 'using Pkg; Pkg.develop(PackageSpec(path=pwd())); Pkg.instantiate()'
23-
- name: Build and deploy
19+
version: '1'
20+
- uses: julia-actions/cache@v3
21+
- uses: julia-actions/julia-buildpkg@v1
22+
- uses: julia-actions/julia-docdeploy@v1
2423
env:
2524
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
2625
DOCUMENTER_KEY: ${{ secrets.DOCUMENTER_KEY }}
27-
run: julia --project=docs/ docs/make.jl

Project.toml

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

66
[deps]
77
HTTP = "cd3eb016-35fb-5094-929b-558a96fad6f3"
8-
LoggingExtras = "e6f89c97-d47a-5376-807f-9c37f3926c36"
98
MIMEs = "6c6e2e6c-3030-632d-7369-2d6c69616d65"
109
Sockets = "6462fe0b-24de-5631-8697-dd941f90decc"
1110
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
1211

13-
[apps]
14-
liveserver = {}
15-
1612
[compat]
17-
HTTP = "1"
13+
HTTP = "2"
1814
MIMEs = "0.1, 1"
19-
LoggingExtras = "1"
20-
julia = "1.6"
15+
julia = "1.10"
16+
17+
[apps.liveserver]

docs/Project.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,6 @@ LiveServer = "16fef848-5104-11e9-1b77-fb7a48bbb589"
44

55
[compat]
66
Documenter = "^0.27"
7+
8+
[sources]
9+
LiveServer = {path = ".."}

src/LiveServer.jl

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ using Base.Filesystem
55
using Base.Threads: @spawn
66

77
using HTTP
8-
using LoggingExtras
98

109
export serve, servedocs
1110

src/server.jl

Lines changed: 15 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -50,21 +50,21 @@ function update_and_close_viewers!(
5050

5151
# send update message to all viewers
5252
@sync for wsᵢ in ws_to_update_and_close
53-
isopen(wsᵢ.io) && @spawn begin
53+
!wsᵢ.writeclosed && @spawn begin
5454
try
5555
HTTP.WebSockets.send(wsᵢ, "update")
5656
catch
5757
end
5858
end
5959
end
6060

61-
# force close all viewers (these will be replaced by 'fresh' ones
62-
# after the reload triggered by the update message)
63-
@sync for wsi in ws_to_update_and_close
64-
isopen(wsi.io) && @spawn begin
61+
# close all viewers in the background — writeclosed is set immediately
62+
# inside close(), causing ws_tracker's loop to exit, while the full WS
63+
# close handshake completes asynchronously without blocking the callback.
64+
for wsi in ws_to_update_and_close
65+
!wsi.writeclosed && @spawn begin
6566
try
66-
wsi.writeclosed = wsi.readclosed = true
67-
close(wsi.io)
67+
close(wsi)
6868
catch
6969
end
7070
end
@@ -485,7 +485,7 @@ function serve_file(
485485
end
486486
push!(headers, "Content-Length" => string(binary_length(content)))
487487
resp = HTTP.Response(ret_code, content)
488-
resp.headers = HTTP.mkheaders(headers)
488+
resp.headers = HTTP.Headers(headers)
489489

490490
# add the file to the file watcher
491491
watch_file!(fw, fs_path)
@@ -518,10 +518,10 @@ function ws_tracker(ws::HTTP.WebSockets.WebSocket)::Nothing
518518
# NOTE: unless we're in the case of a 404, this file always exists because
519519
# the query is generated just after serving it; the 404 case will return an
520520
# empty path.
521-
fs_path, case = get_fs_path(ws.request.target, silent=true)
521+
fs_path, case = get_fs_path(ws.handshake_request.target, silent=true)
522522

523523
if case in (:not_found_with_404, :not_found_without_404)
524-
raw_fs_path, _ = get_fs_path(ws.request.target, onlyfs=true)
524+
raw_fs_path, _ = get_fs_path(ws.handshake_request.target, onlyfs=true)
525525
add_to_viewers(raw_fs_path, ws)
526526
end
527527

@@ -545,7 +545,7 @@ function ws_tracker(ws::HTTP.WebSockets.WebSocket)::Nothing
545545
# forces the websocket to stay open until it's closed by LiveServer (and
546546
# not by the browser) upon writing a `update` message on the websocket.
547547
# See update_and_close_viewers
548-
while isopen(ws.io)
548+
while !ws.writeclosed
549549
sleep(0.1)
550550
end
551551
catch err
@@ -643,15 +643,6 @@ current directory. (See also [`example`](@ref) for an example folder).
643643
)
644644
end
645645

646-
old_logger = global_logger()
647-
old_stderr = stderr
648-
global_logger(
649-
EarlyFilteredLogger(
650-
log -> log._module !== HTTP.Servers,
651-
global_logger()
652-
)
653-
)
654-
655646
server, port = get_server(host, port, req_handler)
656647
host_str = ifelse(host == string(Sockets.localhost), "localhost", host)
657648
url = "http://$host_str:$port"
@@ -692,10 +683,9 @@ current directory. (See also [`example`](@ref) for an example folder).
692683
# close any remaining websockets
693684
for wss values(WS_VIEWERS)
694685
@sync for wsi in wss
695-
isopen(wsi.io) && @spawn begin
686+
!wsi.writeclosed && @spawn begin
696687
try
697-
wsi.writeclosed = wsi.readclosed = true
698-
close(wsi.io)
688+
close(wsi)
699689
catch
700690
end
701691
end
@@ -704,19 +694,13 @@ current directory. (See also [`example`](@ref) for an example folder).
704694
# empty the dictionary of viewers
705695
empty!(WS_VIEWERS)
706696
# shut down the server
707-
HTTP.Servers.forceclose(server)
697+
HTTP.forceclose(server)
708698
# reset other environment variables
709699
reset_content_dir()
710700
reset_ws_interrupt()
711701
println("")
712702
end
713703

714-
# given that LiveServer is interrupted via an InterruptException, we have
715-
# to be extra careful that things are back as they were before, otherwise
716-
# there's a high risk of the disgusting broken pipe error...
717-
redirect_stderr(old_stderr)
718-
global_logger(old_logger)
719-
720704
return nothing
721705
end
722706

@@ -737,7 +721,7 @@ function get_server(
737721

738722
incr >= 10 && @error "couldn't find a free port in $incr tries"
739723
try
740-
server = HTTP.listen!(host, port; readtimeout=0, verbose=-1) do http::HTTP.Stream
724+
server = HTTP.listen!(host, port; read_timeout=0) do http::HTTP.Stream
741725
if HTTP.WebSockets.isupgrade(http.message)
742726
# upgrade to websocket and add to list of viewers and keep open
743727
# until written to

test/Project.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,5 @@ Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
77

88
[compat]
99
Crayons = "4"
10-
HTTP = "1"
11-
MIMEs = "0.1"
10+
HTTP = "2"
11+
MIMEs = "0.1"

test/server.jl

Lines changed: 25 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ tasks that you will try to start.
4848
# define filewatcher outside so that can follow it
4949
fw = LS.SimpleWatcher(LS.file_changed_callback)
5050
task = @spawn serve(fw; port=port)
51-
sleep(0.1) # give it time to get started
51+
timedwait(() -> LS.is_running(fw), 10) # give it time to get started
5252

5353
# there should be a callback associated with fw now
5454
@test fw.status == :runnable
@@ -97,19 +97,17 @@ tasks that you will try to start.
9797
# and then subsequently close a websocket. We check this happens properly by adding
9898
# our own sentinel websocket
9999
function makews()
100-
req = HTTP.Request()
101-
return HTTP.WebSockets.WebSocket(
102-
HTTP.Connection(IOBuffer()), req, req.response; client=false
103-
)
100+
io = IOBuffer(UInt8[], read=true, write=true)
101+
return HTTP.WebSockets.WebSocket(io, () -> nothing; is_client=false)
104102
end
105103
sentinel = makews()
106104
LS.WS_VIEWERS["tmp.html"] = [sentinel]
107105

108-
@test sentinel.io.io.writable
106+
@test !sentinel.writeclosed
109107
write("tmp.html", "something new")
110108
sleep(0.5)
111109
# the sentinel websocket should be closed
112-
@test !sentinel.io.io.writable
110+
@test sentinel.writeclosed
113111
# the websockets should have been flushed
114112
@test isempty(LS.WS_VIEWERS["tmp.html"])
115113

@@ -121,8 +119,8 @@ tasks that you will try to start.
121119
write("css/foo.css", "body { color:blue; }")
122120
sleep(0.5)
123121
# all sentinel websockets should be closed
124-
@test !sentinel1.io.io.writable
125-
@test !sentinel2.io.io.writable
122+
@test sentinel1.writeclosed
123+
@test sentinel2.writeclosed
126124

127125
# Mimic an error when building documentation with Documenter.jl. When this
128126
# happens servedocs_callback!() will set the watcher status to
@@ -174,27 +172,24 @@ end
174172
cd(mktempdir())
175173
write("test_file.html", "Hello!")
176174

177-
server = Sockets.listen(Sockets.localhost, 8001)
178-
io = Sockets.connect(Sockets.localhost, 8001)
179-
key = "k5zQsAMXfFlvmWIE/YCiEg=="
180-
s = HTTP.Stream(
181-
HTTP.Request(
182-
"GET",
183-
"http://localhost:8562/test_file.html",
184-
[
185-
"Connection" => "upgrade",
186-
"Upgrade" => "websocket",
187-
"Sec-WebSocket-Key" => key,
188-
"Sec-WebSocket-Version" => "13"
189-
]
190-
),
191-
HTTP.Connection(io)
192-
)
193-
194-
fs_path, _ = LS.get_fs_path(s.message.target)
175+
target = "/test_file.html"
176+
fs_path, _ = LS.get_fs_path(target)
195177
@test fs_path == "test_file.html"
196178

197-
tsk = @spawn LS.HTTP.WebSockets.upgrade(LS.ws_tracker, s)
179+
function make_tracking_ws(tgt)
180+
io = IOBuffer(UInt8[], read=true, write=true)
181+
ws = HTTP.WebSockets.WebSocket(io, () -> nothing; is_client=false)
182+
ws.handshake_request = HTTP.Request("GET", tgt, [
183+
"Connection" => "upgrade",
184+
"Upgrade" => "websocket",
185+
"Sec-WebSocket-Key" => "k5zQsAMXfFlvmWIE/YCiEg==",
186+
"Sec-WebSocket-Version" => "13"
187+
])
188+
return ws
189+
end
190+
191+
ws1 = make_tracking_ws(target)
192+
tsk = @spawn LS.ws_tracker(ws1)
198193
sleep(1.0)
199194
# the websocket should have been added to the list
200195
@test LS.WS_VIEWERS[fs_path] isa Vector{HTTP.WebSockets.WebSocket}
@@ -207,23 +202,8 @@ end
207202
@test istaskdone(tsk)
208203
@test !LS.WS_INTERRUPT[]
209204

210-
io = Sockets.connect(Sockets.localhost, 8001)
211-
key = "k5zQsAMXfFlvmWIE/YCiEg=="
212-
s = HTTP.Stream(
213-
HTTP.Request(
214-
"GET",
215-
"http://localhost:8562/test_file.html",
216-
[
217-
"Connection" => "upgrade",
218-
"Upgrade" => "websocket",
219-
"Sec-WebSocket-Key" => key,
220-
"Sec-WebSocket-Version" => "13"
221-
]
222-
),
223-
HTTP.Connection(io)
224-
)
225-
226-
tsk = @spawn LS.HTTP.WebSockets.upgrade(LS.ws_tracker, s)
205+
ws2 = make_tracking_ws(target)
206+
tsk = @spawn LS.ws_tracker(ws2)
227207
sleep(1.0)
228208

229209
# simulate a "bad" closure
@@ -235,7 +215,6 @@ end
235215
@test length(LS.WS_VIEWERS[fs_path]) == 2
236216

237217
# cleanup
238-
close(server)
239218
empty!(LS.WS_VIEWERS)
240219

241220
cd(bk)

0 commit comments

Comments
 (0)