Skip to content

Commit b28e291

Browse files
committed
revert to HTTP 0.9 but with #142 #144
1 parent abcd6ad commit b28e291

File tree

4 files changed

+114
-73
lines changed

4 files changed

+114
-73
lines changed

Project.toml

Lines changed: 1 addition & 1 deletion
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.8.3"
4+
version = "0.10.0"
55

66
[deps]
77
Crayons = "a8cc5b0e-0ffa-5ad4-8c14-923d3ee1735f"

src/server.jl

Lines changed: 97 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,7 @@ Return the filesystem path corresponding to a requested path, or an empty
8080
String if the file was not found.
8181
"""
8282
function get_fs_path(req_path::AbstractString)::String
83-
uri = HTTP.URI(req_path)
84-
# first element after the split is **always** "/" --> 2:end
83+
uri = HTTP.URI(req_path)
8584
r_parts = HTTP.URIs.unescapeuri.(split(lstrip(uri.path, '/'), '/'))
8685
fs_path = joinpath(r_parts...)
8786

@@ -95,7 +94,8 @@ function get_fs_path(req_path::AbstractString)::String
9594
isfile(tmp) && return tmp
9695

9796
# content of the dir will be shown
98-
isdir(fs_path) && return fs_path
97+
# we ensure there's a slash at the end (see issue #135)
98+
isdir(fs_path) && return joinpath(fs_path, "")
9999

100100
# 404 will be shown
101101
return ""
@@ -109,7 +109,7 @@ Append `/` to the path part of `url`; i.e., transform `a/b` to `a/b/` and `/a/b?
109109
"""
110110
function append_slash(url_str::AbstractString)
111111
uri = HTTP.URI(url_str)
112-
return string(endswith(uri.path, "/") ? uri : merge(uri; path = uri.path * "/"))
112+
return string(endswith(uri.path, "/") ? uri : HTTP.merge(uri; path = uri.path * "/"))
113113
end
114114

115115
"""
@@ -140,16 +140,18 @@ function get_dir_list(dir::AbstractString)
140140
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/spcss">
141141
<title>Directory listing</title>
142142
<style>
143-
a {text-decoration: none;}
143+
a {text-decoration: none;}
144144
</style>
145145
</head>
146146
<body>
147147
<h1 style='margin-top: 1em;'>
148148
Directory listing
149149
</h1>
150150
<h3>
151-
<a href="/" alt="root">🏠</a> <a href="/$(dirname(dir))" alt="parent dir">⬆️</a> &nbsp; path: <code style='color:gray;'>$(sdir)</code>
152-
</h2>
151+
<a href="/" alt="root">🏠</a>
152+
<a href="/$(dirname(dir))" alt="parent dir">⬆️</a>
153+
&nbsp; path: <code style='color:gray;'>$(sdir)</code>
154+
</h3>
153155
154156
<hr>
155157
<ul>
@@ -160,19 +162,21 @@ function get_dir_list(dir::AbstractString)
160162
list_dirs = [d for d in list if d list_files]
161163

162164
for fname in list_files
163-
link = lstrip_cdir(fname)
164-
name = splitdir(fname)[end]
165-
post = ifelse(islink(fname), " @", "")
165+
link = lstrip_cdir(fname)
166+
name = splitdir(fname)[end]
167+
post = ifelse(islink(fname), " @", "")
166168
write(io, """
167169
<li><a href="/$(link)">$(name)$(post)</a></li>
168170
"""
169171
)
170172
end
171173
for fdir in list_dirs
172-
link = lstrip_cdir(fdir)
173-
name = splitdir(fdir)[end]
174-
pre = "📂 "
175-
post = ifelse(islink(fdir), " @", "")
174+
link = lstrip_cdir(fdir)
175+
# ensure ends with slash, see #135
176+
link *= ifelse(endswith(link, "/"), "", "/")
177+
name = splitdir(fdir)[end]
178+
pre = "📂 "
179+
post = ifelse(islink(fdir), " @", "")
176180
write(io, """
177181
<li><a href="/$(link)">$(pre)$(name)$(post)</a></li>
178182
"""
@@ -197,14 +201,15 @@ to be watched can be added, and a request (e.g. a path entered in a tab of the
197201
browser), and converts it to the appropriate file system path.
198202
199203
The cases are as follows:
200-
1. the path corresponds exactly to a file. If it's a html-like file,
201-
LiveServer will try injecting the reloading `<script>` (see file
202-
`client.html`) at the end, just before the `</body>` tag.
203-
2. the path corresponds to a directory in which there is an `index.html`,
204-
same action as (1) assuming the `index.html` is implicit.
205-
3. the path corresponds to a directory in which there is not an `index.html`,
206-
list the directory contents.
207-
4. not (1,2,3), a 404 is served.
204+
1. FILE: the path corresponds exactly to a file. If it's a html-like file,
205+
LiveServer will try injecting the reloading `<script>` (see file
206+
`client.html`) at the end, just before the `</body>` tag. Otherwise
207+
we let the browser attempt to show it (e.g. if it's an image).
208+
2. WEB-DIRECTORY: the path corresponds to a directory in which there is an
209+
`index.html`, same action as (1) assuming the `index.html` is implicit.
210+
3. PLAIN-DIRECTORY: the path corresponds to a directory in which there is not
211+
an `index.html`, list the directory contents.
212+
4. 404: not (1,2,3), a 404 is served.
208213
209214
All files served are added to the file watcher, which is responsible to check
210215
whether they're already watched or not. Finally the file is served via a 200
@@ -221,9 +226,8 @@ function serve_file(
221226
fs_path = get_fs_path(req.target)
222227

223228
# if get_fs_path returns an empty string, there's two cases:
224-
# 1. the path is a directory without an `index.html` --> list dir
225-
# 2. otherwise serve a 404 (see if there's a dedicated 404 path,
226-
# otherwise just use a basic one).
229+
# 1. [CASE 3] the path is a directory without an `index.html` --> list dir
230+
# 2. [CASE 4] otherwise serve a 404 (see if there's a dedicated 404 path,
227231
if isempty(fs_path)
228232

229233
if req.target == "/"
@@ -251,14 +255,15 @@ function serve_file(
251255
end
252256
end
253257

258+
# [CASE 2]
254259
if isdir(fs_path)
255260
index_page = get_dir_list(fs_path)
256261
return HTTP.Response(200, index_page)
257262
end
258263

259264
# In what follows, fs_path points to a file
260-
# --> html-like: try to inject reload-script
261-
# --> other: just get the browser to show it
265+
# --> [CASE 1a] html-like: try to inject reload-script
266+
# --> [CASE 1b] other: just get the browser to show it
262267
#
263268
ext = lstrip(last(splitext(fs_path)), '.') |> string
264269
content = read(fs_path, String)
@@ -391,59 +396,87 @@ end
391396

392397

393398
"""
394-
serve(filewatcher; host="127.0.0.1", port=8000, dir="", verbose=false, coreloopfun=(c,fw)->nothing, inject_browser_reload_script::Bool = true, launch_browser::Bool = false, allow_cors::Bool = false)
399+
serve(filewatcher; ...)
395400
396401
Main function to start a server at `http://host:port` and render what is in the current
397402
directory. (See also [`example`](@ref) for an example folder).
398403
399404
# Arguments
400405
401-
- `filewatcher` is a file watcher implementing the API described for [`SimpleWatcher`](@ref) (which also is the default) and messaging the viewers (via WebSockets) upon detecting file changes.
402-
- `port` is an integer between 8000 (default) and 9000.
403-
- `dir` specifies where to launch the server if not the current working directory.
404-
- `verbose` is a boolean switch to make the server print information about file changes and connections.
405-
- `coreloopfun` specifies a function which can be run every 0.1 second while the liveserver is going; it takes two arguments: the cycle counter and the filewatcher. By default the coreloop does nothing.
406-
- `launch_browser=false` specifies whether to launch the ambient browser at the localhost URL or not.
407-
- `allow_cors::Bool=false` will allow cross origin (CORS) requests to access the server via the "Access-Control-Allow-Origin" header.
408-
- `preprocess_request=identity`: specifies a function which can transform a request before a response is returned; its only argument is the current request.
409-
410-
# Example
411-
412-
```julia
413-
LiveServer.example()
414-
serve(host="127.0.0.1", port=8080, dir="example", verbose=true, launch_browser=true)
415-
```
416-
417-
You should then see the `index.html` page from the `example` folder being rendered. If you change the file, the browser will automatically reload the
418-
page and show the changes.
419-
"""
420-
function serve(fw::FileWatcher=SimpleWatcher(file_changed_callback);
421-
host::String="127.0.0.1", port::Int=8000, dir::AbstractString="", verbose::Bool=false,
422-
coreloopfun::Function=(c, fw)->nothing,
423-
preprocess_request=identity,
424-
inject_browser_reload_script::Bool = true,
425-
launch_browser::Bool = false,
426-
allow_cors::Bool = false)
427-
428-
8000 port 9000 || throw(ArgumentError("The port must be between 8000 and 9000."))
429-
setverbose(verbose)
430-
431-
if !isempty(dir)
432-
isdir(dir) || throw(ArgumentError("The specified dir '$dir' is not recognised."))
433-
CONTENT_DIR[] = dir
434-
end
406+
`filewatcher`: a file watcher implementing the API described for
407+
[`SimpleWatcher`](@ref) (which also is the default) and
408+
messaging the viewers (via WebSockets) upon detecting file
409+
changes.
410+
`port`: integer between 8000 (default) and 9000.
411+
`dir`: string specifying where to launch the server if not the current
412+
working directory.
413+
`verbose`: boolean switch to make the server print information about file
414+
changes and connections.
415+
`coreloopfun`: function which can be run every 0.1 second while the
416+
liveserver is running; it takes two arguments: the cycle
417+
counter and the filewatcher. By default the coreloop does
418+
nothing.
419+
`launch_browser`: boolean specifying whether to launch the ambient browser
420+
at the localhost or not (default: false).
421+
`allow_cors`: boolean allowing cross origin (CORS) requests to access the
422+
server via the "Access-Control-Allow-Origin" header.
423+
`preprocess_request`: function specifying the transformation of a request
424+
before it is returned; its only argument is the
425+
current request.
426+
# Example
427+
428+
```julia
429+
LiveServer.example()
430+
serve(host="127.0.0.1", port=8080, dir="example", launch_browser=true)
431+
```
432+
433+
You should then see the `index.html` page from the `example` folder being
434+
rendered. If you change the file, the browser will automatically reload the
435+
page and show the changes.
436+
"""
437+
function serve(
438+
fw::FileWatcher=SimpleWatcher(file_changed_callback);
439+
# kwargs
440+
host::String = "127.0.0.1",
441+
port::Int = 8000,
442+
dir::AbstractString = "",
443+
verbose::Bool = false,
444+
coreloopfun::Function = (c, fw)->nothing,
445+
preprocess_request::Function = identity,
446+
inject_browser_reload_script::Bool = true,
447+
launch_browser::Bool = false,
448+
allow_cors::Bool = false
449+
)::Nothing
450+
451+
8000 port 9000 || throw(
452+
ArgumentError("The port must be between 8000 and 9000.")
453+
)
454+
setverbose(verbose)
455+
456+
if !isempty(dir)
457+
isdir(dir) || throw(
458+
ArgumentError("The specified dir '$dir' is not recognised.")
459+
)
460+
CONTENT_DIR[] = dir
461+
end
435462

436463
start(fw)
437464

438465
# make request handler
439466
req_handler = HTTP.RequestHandlerFunction() do req
440467
req = preprocess_request(req)
441-
serve_file(fw, req; inject_browser_reload_script = inject_browser_reload_script, allow_cors = allow_cors)
468+
serve_file(
469+
fw, req;
470+
inject_browser_reload_script = inject_browser_reload_script,
471+
allow_cors = allow_cors
472+
)
442473
end
443474

444475
server = Sockets.listen(parse(IPAddr, host), port)
445476
url = "http://$(host == string(Sockets.localhost) ? "localhost" : host):$port"
446-
println("✓ LiveServer listening on $url/ ...\n (use CTRL+C to shut down)")
477+
println(
478+
"✓ LiveServer listening on $url/ ...\n (use CTRL+C to shut down)"
479+
)
447480
@async HTTP.listen(host, port;
448481
server=server, readtimeout=0, reuse_limit=0) do http::HTTP.Stream
449482
# reuse_limit=0 ensures that there won't be an error if killing and restarting the server.

src/utils.jl

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,18 @@ function servedocs_callback!(
2222
path2makejl::AbstractString,
2323
literate::Union{Nothing,String},
2424
skip_dirs::Vector{String},
25+
skip_files::Vector{String},
2526
foldername::String,
2627
buildfoldername::String)
2728
# ignore things happening in the build folder (generated files etc)
2829
startswith(fp, joinpath(foldername, buildfoldername)) && return nothing
29-
# ignore things happening in any skip_dirs
30+
# ignore files skip_dirs and skip_files
3031
for dir in skip_dirs
31-
startswith(fp, dir) && return nothing
32-
end
32+
startswith(fp, dir) && return nothing
33+
end
34+
for file in skip_files
35+
fp == file && return nothing
36+
end
3337

3438
# if the file that was changed is the `make.jl` file, assume that maybe
3539
# new files have been generated and so refresh the vector of watched files
@@ -167,6 +171,7 @@ subfolder `docs`.
167171
`skip_dir=joinpath("docs","src","examples")`.
168172
* `skip_dirs=[]`: same as `skip_dir` but for a list of such dirs. Takes
169173
precedence over `skip_dir`.
174+
* `skip_files=[]`: a vector of files that should not trigger regeneration.
170175
* `foldername="docs"`: specify a different path for the content.
171176
* `buildfoldername="build"`: specify a different path for the build.
172177
* `makejl="make.jl"`: path of the script generating the documentation relative
@@ -182,6 +187,7 @@ function servedocs(;
182187
doc_env::Bool=false,
183188
skip_dir::String="",
184189
skip_dirs::Vector{String}=String[],
190+
skip_files::Vector{String}=String[],
185191
foldername::String="docs",
186192
buildfoldername::String="build",
187193
makejl::String="make.jl",
@@ -193,6 +199,8 @@ function servedocs(;
193199
if isempty(skip_dirs) && !isempty(skip_dir)
194200
skip_dirs = [skip_dir]
195201
end
202+
skip_dirs = abspath.(skip_dirs)
203+
skip_files = abspath.(skip_files)
196204

197205
path2makejl = joinpath(foldername, makejl)
198206

@@ -203,7 +211,7 @@ function servedocs(;
203211
docwatcher,
204212
fp -> servedocs_callback!(
205213
docwatcher, fp, path2makejl,
206-
literate, skip_dirs, foldername, buildfoldername
214+
literate, skip_dirs, skip_files, foldername, buildfoldername
207215
)
208216
)
209217

test/utils.jl

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
include(abspath(makejl))
2424
@test readmake() == 1
2525

26-
def = (nothing, String[], "docs", "build")
26+
def = (nothing, String[], String[], "docs", "build")
2727

2828
# callback function
2929
dw = LS.SimpleWatcher()
@@ -107,7 +107,7 @@ end
107107
# callback function
108108
dw = LS.SimpleWatcher()
109109

110-
LS.servedocs_callback!(dw, makejl, makejl, "", String[], "site", "build")
110+
LS.servedocs_callback!(dw, makejl, makejl, "", String[], String[], "site", "build")
111111

112112
@test length(dw.watchedfiles) == 3
113113
@test dw.watchedfiles[1].path == joinpath("site", "make.jl")
@@ -118,14 +118,14 @@ end
118118

119119
# let's now remove `index2.md`
120120
rm(joinpath("site", "src", "index2.md"))
121-
LS.servedocs_callback!(dw, makejl, makejl, "", String[], "site", "build")
121+
LS.servedocs_callback!(dw, makejl, makejl, "", String[], String[], "site", "build")
122122

123123
# the file has been removed
124124
@test length(dw.watchedfiles) == 2
125125
@test readmake() == 3
126126

127127
# let's check there's an appropriate trigger for index
128-
LS.servedocs_callback!(dw, joinpath("site", "src", "index.md"), makejl, "", String[], "site", "build")
128+
LS.servedocs_callback!(dw, joinpath("site", "src", "index.md"), makejl, "", String[], String[], "site", "build")
129129
@test length(dw.watchedfiles) == 2
130130
@test readmake() == 4
131131

0 commit comments

Comments
 (0)