@@ -83,8 +83,7 @@ Return the filesystem path corresponding to a requested path, or an empty
83
83
String if the file was not found.
84
84
"""
85
85
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)
88
87
r_parts = HTTP. URIs. unescapeuri .(split (lstrip (uri. path, ' /' ), ' /' ))
89
88
fs_path = joinpath (r_parts... )
90
89
@@ -98,7 +97,8 @@ function get_fs_path(req_path::AbstractString)::String
98
97
isfile (tmp) && return tmp
99
98
100
99
# 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, " " )
102
102
103
103
# 404 will be shown
104
104
return " "
@@ -143,16 +143,18 @@ function get_dir_list(dir::AbstractString)
143
143
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/spcss">
144
144
<title>Directory listing</title>
145
145
<style>
146
- a {text-decoration: none;}
146
+ a {text-decoration: none;}
147
147
</style>
148
148
</head>
149
149
<body>
150
150
<h1 style='margin-top: 1em;'>
151
151
Directory listing
152
152
</h1>
153
153
<h3>
154
- <a href="/" alt="root">🏠</a> <a href="/$(dirname (dir)) " alt="parent dir">⬆️</a> 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
+ path: <code style='color:gray;'>$(sdir) </code>
157
+ </h3>
156
158
157
159
<hr>
158
160
<ul>
@@ -163,19 +165,21 @@ function get_dir_list(dir::AbstractString)
163
165
list_dirs = [d for d in list if d ∉ list_files]
164
166
165
167
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), " @" , " " )
169
171
write (io, """
170
172
<li><a href="/$(link) ">$(name)$(post) </a></li>
171
173
"""
172
174
)
173
175
end
174
176
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), " @" , " " )
179
183
write (io, """
180
184
<li><a href="/$(link) ">$(pre)$(name)$(post) </a></li>
181
185
"""
@@ -200,14 +204,16 @@ to be watched can be added, and a request (e.g. a path entered in a tab of the
200
204
browser), and converts it to the appropriate file system path.
201
205
202
206
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,
204
209
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.
211
217
212
218
All files served are added to the file watcher, which is responsible to check
213
219
whether they're already watched or not. Finally the file is served via a 200
@@ -224,8 +230,8 @@ function serve_file(
224
230
fs_path = get_fs_path (req. target)
225
231
226
232
# 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,
229
235
# otherwise just use a basic one).
230
236
if isempty (fs_path)
231
237
@@ -254,14 +260,15 @@ function serve_file(
254
260
end
255
261
end
256
262
263
+ # [CASE 2]
257
264
if isdir (fs_path)
258
265
index_page = get_dir_list (fs_path)
259
266
return HTTP. Response (200 , index_page)
260
267
end
261
268
262
269
# 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
265
272
#
266
273
ext = lstrip (last (splitext (fs_path)), ' .' ) |> string
267
274
content = read (fs_path, String)
@@ -381,45 +388,68 @@ end
381
388
382
389
383
390
"""
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... )
385
392
386
393
Main function to start a server at `http://host:port` and render what is in the current
387
394
directory. (See also [`example`](@ref) for an example folder).
388
395
389
396
# Arguments
390
397
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.
399
418
400
419
# Example
401
420
402
421
```julia
403
422
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)
405
424
```
406
425
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
408
428
page and show the changes.
409
429
"""
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
+ )
419
447
setverbose (verbose)
420
448
421
449
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
+ )
423
453
CONTENT_DIR[] = dir
424
454
end
425
455
@@ -428,11 +458,17 @@ function serve(fw::FileWatcher=SimpleWatcher(file_changed_callback);
428
458
# make request handler
429
459
req_handler = HTTP. Handlers. streamhandler () do req
430
460
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
+ )
432
466
end
433
467
434
468
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
+ )
436
472
server = HTTP. listen! (host, port; readtimeout= 0 ) do http:: HTTP.Stream
437
473
if HTTP. WebSockets. isupgrade (http. message)
438
474
# upgrade to websocket and add to list of viewers and keep open until written to
0 commit comments