Skip to content

Commit 7dded62

Browse files
authored
closes #135 (#144)
1 parent a196908 commit 7dded62

File tree

2 files changed

+84
-48
lines changed

2 files changed

+84
-48
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.9.1"
4+
version = "0.9.2"
55

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

src/server.jl

Lines changed: 83 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,7 @@ Return the filesystem path corresponding to a requested path, or an empty
8383
String if the file was not found.
8484
"""
8585
function get_fs_path(req_path::AbstractString)::String
86-
uri = HTTP.URI(req_path)
87-
# first element after the split is **always** "/" --> 2:end
86+
uri = HTTP.URI(req_path)
8887
r_parts = HTTP.URIs.unescapeuri.(split(lstrip(uri.path, '/'), '/'))
8988
fs_path = joinpath(r_parts...)
9089

@@ -98,7 +97,8 @@ function get_fs_path(req_path::AbstractString)::String
9897
isfile(tmp) && return tmp
9998

10099
# content of the dir will be shown
101-
isdir(fs_path) && return fs_path
100+
# we ensure there's a slash at the end (see issue #135)
101+
isdir(fs_path) && return joinpath(fs_path, "")
102102

103103
# 404 will be shown
104104
return ""
@@ -143,16 +143,18 @@ function get_dir_list(dir::AbstractString)
143143
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/spcss">
144144
<title>Directory listing</title>
145145
<style>
146-
a {text-decoration: none;}
146+
a {text-decoration: none;}
147147
</style>
148148
</head>
149149
<body>
150150
<h1 style='margin-top: 1em;'>
151151
Directory listing
152152
</h1>
153153
<h3>
154-
<a href="/" alt="root">🏠</a> <a href="/$(dirname(dir))" alt="parent dir">⬆️</a> &nbsp; path: <code style='color:gray;'>$(sdir)</code>
155-
</h2>
154+
<a href="/" alt="root">🏠</a>
155+
<a href="/$(dirname(dir))" alt="parent dir">⬆️</a>
156+
&nbsp; path: <code style='color:gray;'>$(sdir)</code>
157+
</h3>
156158
157159
<hr>
158160
<ul>
@@ -163,19 +165,21 @@ function get_dir_list(dir::AbstractString)
163165
list_dirs = [d for d in list if d list_files]
164166

165167
for fname in list_files
166-
link = lstrip_cdir(fname)
167-
name = splitdir(fname)[end]
168-
post = ifelse(islink(fname), " @", "")
168+
link = lstrip_cdir(fname)
169+
name = splitdir(fname)[end]
170+
post = ifelse(islink(fname), " @", "")
169171
write(io, """
170172
<li><a href="/$(link)">$(name)$(post)</a></li>
171173
"""
172174
)
173175
end
174176
for fdir in list_dirs
175-
link = lstrip_cdir(fdir)
176-
name = splitdir(fdir)[end]
177-
pre = "📂 "
178-
post = ifelse(islink(fdir), " @", "")
177+
link = lstrip_cdir(fdir)
178+
# ensure ends with slash, see #135
179+
link *= ifelse(endswith(link, "/"), "", "/")
180+
name = splitdir(fdir)[end]
181+
pre = "📂 "
182+
post = ifelse(islink(fdir), " @", "")
179183
write(io, """
180184
<li><a href="/$(link)">$(pre)$(name)$(post)</a></li>
181185
"""
@@ -200,14 +204,16 @@ to be watched can be added, and a request (e.g. a path entered in a tab of the
200204
browser), and converts it to the appropriate file system path.
201205
202206
The cases are as follows:
203-
1. the path corresponds exactly to a file. If it's a html-like file,
207+
208+
1. FILE: the path corresponds exactly to a file. If it's a html-like file,
204209
LiveServer will try injecting the reloading `<script>` (see file
205-
`client.html`) at the end, just before the `</body>` tag.
206-
2. the path corresponds to a directory in which there is an `index.html`,
207-
same action as (1) assuming the `index.html` is implicit.
208-
3. the path corresponds to a directory in which there is not an `index.html`,
209-
list the directory contents.
210-
4. not (1,2,3), a 404 is served.
210+
`client.html`) at the end, just before the `</body>` tag. Otherwise
211+
we let the browser attempt to show it (e.g. if it's an image).
212+
2. WEB-DIRECTORY: the path corresponds to a directory in which there is an
213+
`index.html`, same action as (1) assuming the `index.html` is implicit.
214+
3. PLAIN-DIRECTORY: the path corresponds to a directory in which there is not
215+
an `index.html`, list the directory contents.
216+
4. 404: not (1,2,3), a 404 is served.
211217
212218
All files served are added to the file watcher, which is responsible to check
213219
whether they're already watched or not. Finally the file is served via a 200
@@ -224,8 +230,8 @@ function serve_file(
224230
fs_path = get_fs_path(req.target)
225231

226232
# if get_fs_path returns an empty string, there's two cases:
227-
# 1. the path is a directory without an `index.html` --> list dir
228-
# 2. otherwise serve a 404 (see if there's a dedicated 404 path,
233+
# 1. [CASE 3] the path is a directory without an `index.html` --> list dir
234+
# 2. [CASE 4] otherwise serve a 404 (see if there's a dedicated 404 path,
229235
# otherwise just use a basic one).
230236
if isempty(fs_path)
231237

@@ -254,14 +260,15 @@ function serve_file(
254260
end
255261
end
256262

263+
# [CASE 2]
257264
if isdir(fs_path)
258265
index_page = get_dir_list(fs_path)
259266
return HTTP.Response(200, index_page)
260267
end
261268

262269
# In what follows, fs_path points to a file
263-
# --> html-like: try to inject reload-script
264-
# --> other: just get the browser to show it
270+
# --> [CASE 1a] html-like: try to inject reload-script
271+
# --> [CASE 1b] other: just get the browser to show it
265272
#
266273
ext = lstrip(last(splitext(fs_path)), '.') |> string
267274
content = read(fs_path, String)
@@ -381,45 +388,68 @@ end
381388

382389

383390
"""
384-
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)
391+
serve(filewatcher; kw...)
385392
386393
Main function to start a server at `http://host:port` and render what is in the current
387394
directory. (See also [`example`](@ref) for an example folder).
388395
389396
# Arguments
390397
391-
- `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.
392-
- `port` is an integer between 8000 (default) and 9000.
393-
- `dir` specifies where to launch the server if not the current working directory.
394-
- `verbose` is a boolean switch to make the server print information about file changes and connections.
395-
- `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.
396-
- `launch_browser=false` specifies whether to launch the ambient browser at the localhost URL or not.
397-
- `allow_cors::Bool=false` will allow cross origin (CORS) requests to access the server via the "Access-Control-Allow-Origin" header.
398-
- `preprocess_request=identity`: specifies a function which can transform a request before a response is returned; its only argument is the current request.
398+
`filewatcher`: a file watcher implementing the API described for
399+
[`SimpleWatcher`](@ref) (which also is the default) and
400+
messaging the viewers (via WebSockets) upon detecting file
401+
changes.
402+
`port`: integer between 8000 (default) and 9000.
403+
`dir`: string specifying where to launch the server if not the current
404+
working directory.
405+
`verbose`: boolean switch to make the server print information about file
406+
changes and connections.
407+
`coreloopfun`: function which can be run every 0.1 second while the
408+
liveserver is running; it takes two arguments: the cycle
409+
counter and the filewatcher. By default the coreloop does
410+
nothing.
411+
`launch_browser`: boolean specifying whether to launch the ambient browser
412+
at the localhost or not (default: false).
413+
`allow_cors`: boolean allowing cross origin (CORS) requests to access the
414+
server via the "Access-Control-Allow-Origin" header.
415+
`preprocess_request`: function specifying the transformation of a request
416+
before it is returned; its only argument is the
417+
current request.
399418
400419
# Example
401420
402421
```julia
403422
LiveServer.example()
404-
serve(host="127.0.0.1", port=8080, dir="example", verbose=true, launch_browser=true)
423+
serve(host="127.0.0.1", port=8080, dir="example", launch_browser=true)
405424
```
406425
407-
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
426+
You should then see the `index.html` page from the `example` folder being
427+
rendered. If you change the file, the browser will automatically reload the
408428
page and show the changes.
409429
"""
410-
function serve(fw::FileWatcher=SimpleWatcher(file_changed_callback);
411-
host::String="127.0.0.1", port::Int=8000, dir::AbstractString="", verbose::Bool=false,
412-
coreloopfun::Function=(c, fw)->nothing,
413-
preprocess_request=identity,
414-
inject_browser_reload_script::Bool = true,
415-
launch_browser::Bool = false,
416-
allow_cors::Bool = false)
417-
418-
8000 port 9000 || throw(ArgumentError("The port must be between 8000 and 9000."))
430+
function serve(
431+
fw::FileWatcher=SimpleWatcher(file_changed_callback);
432+
# kwargs
433+
host::String = "127.0.0.1",
434+
port::Int = 8000,
435+
dir::AbstractString = "",
436+
verbose::Bool = false,
437+
coreloopfun::Function = (c, fw)->nothing,
438+
preprocess_request::Function = identity,
439+
inject_browser_reload_script::Bool = true,
440+
launch_browser::Bool = false,
441+
allow_cors::Bool = false
442+
)::Nothing
443+
444+
8000 port 9000 || throw(
445+
ArgumentError("The port must be between 8000 and 9000.")
446+
)
419447
setverbose(verbose)
420448

421449
if !isempty(dir)
422-
isdir(dir) || throw(ArgumentError("The specified dir '$dir' is not recognised."))
450+
isdir(dir) || throw(
451+
ArgumentError("The specified dir '$dir' is not recognised.")
452+
)
423453
CONTENT_DIR[] = dir
424454
end
425455

@@ -428,11 +458,17 @@ function serve(fw::FileWatcher=SimpleWatcher(file_changed_callback);
428458
# make request handler
429459
req_handler = HTTP.Handlers.streamhandler() do req
430460
req = preprocess_request(req)
431-
serve_file(fw, req; inject_browser_reload_script = inject_browser_reload_script, allow_cors = allow_cors)
461+
serve_file(
462+
fw, req;
463+
inject_browser_reload_script = inject_browser_reload_script,
464+
allow_cors = allow_cors
465+
)
432466
end
433467

434468
url = "http://$(host == string(Sockets.localhost) ? "localhost" : host):$port"
435-
println("✓ LiveServer listening on $url/ ...\n (use CTRL+C to shut down)")
469+
println(
470+
"✓ LiveServer listening on $url/ ...\n (use CTRL+C to shut down)"
471+
)
436472
server = HTTP.listen!(host, port; readtimeout=0) do http::HTTP.Stream
437473
if HTTP.WebSockets.isupgrade(http.message)
438474
# upgrade to websocket and add to list of viewers and keep open until written to

0 commit comments

Comments
 (0)