Skip to content

Commit 5c5762e

Browse files
committed
fixing a bunch of things
1 parent b71de33 commit 5c5762e

File tree

1 file changed

+105
-65
lines changed

1 file changed

+105
-65
lines changed

src/server.jl

Lines changed: 105 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -80,20 +80,23 @@ String if the file was not found.
8080
"""
8181
function get_fs_path(req_path::AbstractString)::String
8282
uri = HTTP.URI(req_path)
83-
# first element after the split is **always** "/"
84-
r_parts = HTTP.URIs.unescapeuri.(split(uri.path[2:end], "/"))
83+
# first element after the split is **always** "/" --> 2:end
84+
r_parts = HTTP.URIs.unescapeuri.(split(lstrip(uri.path, '/'), "/"))
8585
fs_path = joinpath(r_parts...)
86+
8687
if !isempty(CONTENT_DIR[])
8788
fs_path = joinpath(CONTENT_DIR[], fs_path)
8889
end
89-
# if no file is specified, try to append `index.html` and see
90-
endswith(uri.path, "/") && (fs_path = joinpath(fs_path, "index.html"))
91-
# either the result is a valid file path in which case it's returned otherwise ""
92-
if isfile(fs_path) || isdir(fs_path)
93-
return fs_path
94-
else
95-
return ""
90+
91+
# if it ends with `/` try to see if there's an index.html
92+
if endswith(uri.path, '/')
93+
tmp = joinpath(fs_path, "index.html")
94+
isfile(tmp) && return tmp
9695
end
96+
# otherwise return the path if it exists (file or dir)
97+
(isfile(fs_path) || isdir(fs_path)) && return fs_path
98+
# otherwise --> this will lead to a 404
99+
return ""
97100
end
98101

99102
"""
@@ -113,58 +116,55 @@ end
113116
Generate list of content at path `dir`.
114117
"""
115118
function get_dir_list(dir::AbstractString)
116-
path = joinpath(abspath(CONTENT_DIR[]), lstrip(dir, ['/', '\\']))
117-
if isdir(path)
118-
list = readdir(path; join=false, sort=true)
119-
end
120-
io = IOBuffer()
121-
enc = "utf-8" # sys.getfilesystemencoding()
122-
title = "Directory listing for $(dir)"
119+
list = readdir(dir; join=true, sort=true)
120+
io = IOBuffer()
123121
write(io, """
124122
<!DOCTYPE HTML>
125123
<html>
126124
<head>
127125
<meta charset="utf-8">
128126
<meta name="viewport" content="width=device-width, initial-scale=1">
129-
<title>$(title)</title>
127+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/spcss">
128+
<title>Directory listing for '$dir'</title>
129+
<style>
130+
a {text-decoration: none;}
131+
</style>
130132
</head>
131133
<body>
132-
<h1>$(title)</h1>
133-
<hr>
134+
<h1 style='margin-top: 3em;'>
135+
Directory listing for '<code style='color:gray;'>$(dir)</code>'
136+
</h1>
137+
<br> <hr>
134138
<ul>
135139
"""
136140
)
137141

138-
if isfile(path)
139-
linkname = path
140-
displayname = basename(path)
142+
list_files = [f for f in list if isfile(f)]
143+
list_dirs = [d for d in list if d list_files]
144+
145+
for fullname in list_files
146+
name = splitdir(fullname)[end]
147+
post = ifelse(islink(fullname), " @", "")
141148
write(io, """
142-
<li><a href="$(linkname)">$(displayname)</a></li>
149+
<li><a href="/$(fullname)">$(name)$(post)</a></li>
143150
"""
144151
)
145-
empty!(list)
146152
end
147-
148-
for name in list
149-
fullname = joinpath(path, name)
150-
displayname = linkname = name
151-
# Append / for directories or @ for symbolic links
152-
if isdir(fullname)
153-
displayname = name * "/"
154-
linkname = name * "/"
155-
end
156-
if islink(fullname)
157-
displayname = name * "@"
158-
# Note: a link to a directory displays with @ and links with /
159-
end
153+
for fullname in list_dirs
154+
name = splitdir(fullname)[end]
155+
pre = "📂 "
156+
post = ifelse(islink(fullname), " @", "")
160157
write(io, """
161-
<li><a href="$(linkname)">$(displayname)</a></li>
158+
<li><a href="/$(fullname)">$(pre)$(name)$(post)</a></li>
162159
"""
163160
)
164161
end
165162
write(io, """
166163
</ul>
167-
<hr>
164+
<hr> <br>
165+
<a href="/">🏠 root</a>
166+
<br>
167+
<a href="https://github.com/tlienart/LiveServer.jl">💻 LiveServer.jl</a>
168168
</body>
169169
</html>
170170
"""
@@ -173,21 +173,47 @@ function get_dir_list(dir::AbstractString)
173173
end
174174

175175
"""
176-
serve_file(fw, req::HTTP.Request; inject_browser_reload_script::Bool = true)
177-
178-
Handler function for serving files. This takes a file watcher, to which files to be watched can be
179-
added, and a request (e.g. a path entered in a tab of the browser), and converts it to the
180-
appropriate file system path. If the path corresponds to a HTML file, it will inject the reloading
181-
`<script>` (see file `client.html`) at the end of its body, i.e. directly before the `</body>` tag.
182-
All files served are added to the file watcher, which is responsible to check whether they're
183-
already watched or not. Finally the file is served via a 200 (successful) response. If the file
184-
does not exist, a response with status 404 and an according message is sent.
176+
serve_file(fw, req::HTTP.Request; inject_browser_reload_script = true)
177+
178+
Handler function for serving files. This takes a file watcher, to which files
179+
to be watched can be added, and a request (e.g. a path entered in a tab of the
180+
browser), and converts it to the appropriate file system path.
181+
182+
The cases are as follows:
183+
1. the path corresponds exactly to a file. If it's a html-like file,
184+
LiveServer will try injecting the reloading `<script>` (see file
185+
`client.html`) at the end, just before the `</body>` tag.
186+
2. the path corresponds to a directory in which there is an `index.html`,
187+
same action as (1) assuming the `index.html` is implicit.
188+
3. the path corresponds to a directory in which there is not an `index.html`,
189+
list the directory contents.
190+
4. not (1,2,3), a 404 is served.
191+
192+
All files served are added to the file watcher, which is responsible to check
193+
whether they're already watched or not. Finally the file is served via a 200
194+
(successful) response. If the file does not exist, a response with status 404
195+
and message is returned.
185196
"""
186-
function serve_file(fw, req::HTTP.Request; inject_browser_reload_script::Bool = true, allow_cors::Bool = false)
197+
function serve_file(
198+
fw, req::HTTP.Request;
199+
inject_browser_reload_script::Bool = true,
200+
allow_cors::Bool = false
201+
)::HTTP.Response
202+
187203
ret_code = 200
188-
fs_path = get_fs_path(req.target)
189-
# in case the path was not resolved, return a 404
204+
fs_path = get_fs_path(req.target)
205+
206+
# if get_fs_path returns an empty string, there's two cases:
207+
# 1. the path is a directory without an `index.html` --> list dir
208+
# 2. otherwise serve a 404 (see if there's a dedicated 404 path,
209+
# otherwise just use a basic one).
190210
if isempty(fs_path)
211+
212+
if req.target == "/"
213+
index_page = get_dir_list(".")
214+
return HTTP.Response(200, index_page)
215+
end
216+
191217
ret_code = 404
192218
# Check if /404/ or /404.html exists and serve that as a body
193219
for f in ("/404/", "/404.html")
@@ -197,30 +223,43 @@ function serve_file(fw, req::HTTP.Request; inject_browser_reload_script::Bool =
197223
break
198224
end
199225
end
226+
200227
# If still not found a body, return a generic error message
201228
if isempty(fs_path)
202-
index_page = get_dir_list(req.target)
203-
return HTTP.Response(200, index_page)
229+
return HTTP.Response(404, """
230+
404: file not found. Perhaps you made a typo in the URL,
231+
or the requested file has been deleted or renamed.
232+
"""
233+
)
204234
end
205235
end
206236

207-
# Respond with 301 if the path is a directory
208237
if isdir(fs_path)
209-
return HTTP.Response(301, ["Location" => append_slash(req.target)])
238+
index_page = get_dir_list(fs_path)
239+
return HTTP.Response(200, index_page)
210240
end
211241

242+
#
243+
# In what follows, fs_path points to a file
244+
# --> html-like: try to inject reload-script
245+
# --> other: just get the browser to show it
246+
#
247+
212248
ext = last(splitext(fs_path))[2:end]
213249
content = read(fs_path, String)
214250

215251
# build the response with appropriate mime type (this is inspired from Mux
216252
# https://github.com/JuliaWeb/Mux.jl/blob/master/src/examples/files.jl)
217-
mime = get(MIME_TYPES, ext, "application/octet-stream")
253+
mime = get(MIME_TYPES, ext, "text/plain")
218254

219-
# if html, add the browser-sync script to it
220-
if (inject_browser_reload_script) && (mime in ["text/html", "application/xhtml+xml"])
255+
# if html-like, try adding the browser-sync script to it
256+
inject_reload = inject_browser_reload_script &&
257+
mime in ("text/html", "application/xhtml+xml")
258+
if inject_reload
221259
end_body_match = match(r"</body>", content)
222260
if end_body_match === nothing
223-
# no </body> tag found, trying to add the reload script at the end; this may fail.
261+
# no </body> tag found, trying to add the reload script at the
262+
# end. This may fail.
224263
content *= BROWSER_RELOAD_SCRIPT
225264
else
226265
end_body = prevind(content, end_body_match.offset)
@@ -237,8 +276,8 @@ function serve_file(fw, req::HTTP.Request; inject_browser_reload_script::Bool =
237276
if allow_cors
238277
push!(headers, "Access-Control-Allow-Origin" => "*")
239278
end
240-
resp = HTTP.Response(ret_code, content)
241-
isempty(headers) || (resp.headers = HTTP.mkheaders(headers))
279+
resp = HTTP.Response(ret_code, content)
280+
resp.headers = HTTP.mkheaders(headers)
242281

243282
# add the file to the file watcher
244283
watch_file!(fw, fs_path)
@@ -275,7 +314,8 @@ corresponding to the targeted file.
275314
"""
276315
function ws_tracker(ws::HTTP.WebSockets.WebSocket, target::AbstractString)
277316
# add to list of html files being "watched"
278-
# NOTE: this file always exists because the query is generated just after serving it
317+
# NOTE: this file always exists because the query is generated just after
318+
# serving it
279319
fs_path = get_fs_path(target)
280320

281321
# if the file is already being viewed, add ws to it (e.g. several tabs)
@@ -301,9 +341,9 @@ function ws_tracker(ws::HTTP.WebSockets.WebSocket, target::AbstractString)
301341
# we make the assumption it's always the case (note that it can cause other
302342
# errors than InterruptException, for instance it can cause errors due to
303343
# stream not being available etc but these all have the same source).
304-
# - We therefore do not propagate the error but merely store the information that
305-
# there was a forcible interruption of the websocket so that the interruption
306-
# can be guaranteed to be propagated.
344+
# - We therefore do not propagate the error but merely store the information
345+
# that there was a forcible interruption of the websocket so that the
346+
# interruption can be guaranteed to be propagated.
307347
WS_INTERRUPT[] = true
308348
end
309349
return nothing

0 commit comments

Comments
 (0)