@@ -80,20 +80,23 @@ String if the file was not found.
80
80
"""
81
81
function get_fs_path (req_path:: AbstractString ):: String
82
82
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, ' / ' ) , " /" ))
85
85
fs_path = joinpath (r_parts... )
86
+
86
87
if ! isempty (CONTENT_DIR[])
87
88
fs_path = joinpath (CONTENT_DIR[], fs_path)
88
89
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
96
95
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 " "
97
100
end
98
101
99
102
"""
@@ -113,58 +116,55 @@ end
113
116
Generate list of content at path `dir`.
114
117
"""
115
118
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 ()
123
121
write (io, """
124
122
<!DOCTYPE HTML>
125
123
<html>
126
124
<head>
127
125
<meta charset="utf-8">
128
126
<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>
130
132
</head>
131
133
<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>
134
138
<ul>
135
139
"""
136
140
)
137
141
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), " @" , " " )
141
148
write (io, """
142
- <li><a href="$(linkname ) ">$(displayname ) </a></li>
149
+ <li><a href="/ $(fullname ) ">$(name)$(post ) </a></li>
143
150
"""
144
151
)
145
- empty! (list)
146
152
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), " @" , " " )
160
157
write (io, """
161
- <li><a href="$(linkname ) ">$(displayname ) </a></li>
158
+ <li><a href="/ $(fullname ) ">$(pre)$(name)$(post ) </a></li>
162
159
"""
163
160
)
164
161
end
165
162
write (io, """
166
163
</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>
168
168
</body>
169
169
</html>
170
170
"""
@@ -173,21 +173,47 @@ function get_dir_list(dir::AbstractString)
173
173
end
174
174
175
175
"""
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.
185
196
"""
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
+
187
203
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).
190
210
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
+
191
217
ret_code = 404
192
218
# Check if /404/ or /404.html exists and serve that as a body
193
219
for f in (" /404/" , " /404.html" )
@@ -197,30 +223,43 @@ function serve_file(fw, req::HTTP.Request; inject_browser_reload_script::Bool =
197
223
break
198
224
end
199
225
end
226
+
200
227
# If still not found a body, return a generic error message
201
228
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
+ )
204
234
end
205
235
end
206
236
207
- # Respond with 301 if the path is a directory
208
237
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)
210
240
end
211
241
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
+
212
248
ext = last (splitext (fs_path))[2 : end ]
213
249
content = read (fs_path, String)
214
250
215
251
# build the response with appropriate mime type (this is inspired from Mux
216
252
# 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 " )
218
254
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
221
259
end_body_match = match (r" </body>" , content)
222
260
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.
224
263
content *= BROWSER_RELOAD_SCRIPT
225
264
else
226
265
end_body = prevind (content, end_body_match. offset)
@@ -237,8 +276,8 @@ function serve_file(fw, req::HTTP.Request; inject_browser_reload_script::Bool =
237
276
if allow_cors
238
277
push! (headers, " Access-Control-Allow-Origin" => " *" )
239
278
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)
242
281
243
282
# add the file to the file watcher
244
283
watch_file! (fw, fs_path)
@@ -275,7 +314,8 @@ corresponding to the targeted file.
275
314
"""
276
315
function ws_tracker (ws:: HTTP.WebSockets.WebSocket , target:: AbstractString )
277
316
# 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
279
319
fs_path = get_fs_path (target)
280
320
281
321
# 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)
301
341
# we make the assumption it's always the case (note that it can cause other
302
342
# errors than InterruptException, for instance it can cause errors due to
303
343
# 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.
307
347
WS_INTERRUPT[] = true
308
348
end
309
349
return nothing
0 commit comments