Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HTTP::StaticFileHandler: Reduce max stat calls from 6 to 2 #12310

Merged
merged 2 commits into from
Jul 29, 2022
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 11 additions & 11 deletions src/http/server/handlers/static_file_handler.cr
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,9 @@ class HTTP::StaticFileHandler
expanded_path = request_path.expand("/")

file_path = @public_dir.join(expanded_path.to_kind(Path::Kind.native))
is_dir = Dir.exists? file_path
is_file = !is_dir && File.exists?(file_path)
file_info = File.info? file_path
is_dir = file_info && file_info.directory?
is_file = file_info && file_info.file?
Comment on lines +58 to +59
Copy link
Contributor

@Sija Sija Jul 26, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor suggestion:

Suggested change
is_dir = file_info && file_info.directory?
is_file = file_info && file_info.file?
is_dir = file_info.try(&.directory?)
is_file = file_info.try(&.file?)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

&& is an attempt to reduce the type to Bool.

@asterite Does optimize anything?

Copy link
Member

@straight-shoota straight-shoota Jul 27, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The result of both expression is identical. It's not bool because Nil#&& returns Nil.

See https://crystal-lang.org/reference/1.5/syntax_and_semantics/and.html

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My opinion on these suggestions is that they are just suggestions. It depends on your code style, and the guide says nothing about this. So feel free to pick them up or ignore them.


if request_path != expanded_path || is_dir && !is_dir_path
redirect_path = expanded_path
Expand All @@ -67,11 +68,13 @@ class HTTP::StaticFileHandler
return
end

return call_next(context) unless file_info

if @directory_listing && is_dir
context.response.content_type = "text/html"
directory_listing(context.response, request_path, file_path)
elsif is_file
last_modified = modification_time(file_path)
last_modified = file_info.modification_time
add_cache_headers(context.response.headers, last_modified)

if cache_request?(context, last_modified)
Expand All @@ -85,21 +88,22 @@ class HTTP::StaticFileHandler
if context.request.headers.includes_word?("Accept-Encoding", "gzip")
gz_file_path = "#{file_path}.gz"

if File.exists?(gz_file_path) &&
if (gz_file_info = File.info?(gz_file_path)) &&
# Allow small time drift. In some file systems, using `gz --keep` to
# compress the file will keep the modification time of the original file
# but truncating some decimals
last_modified - modification_time(gz_file_path) < 1.millisecond
last_modified - gz_file_info.modification_time < 1.millisecond
file_path = gz_file_path
file_info = gz_file_info
context.response.headers["Content-Encoding"] = "gzip"
end
end

context.response.content_length = File.size(file_path)
context.response.content_length = file_info.size
File.open(file_path) do |file|
IO.copy(file, context.response)
end
else
else # Not a normal file (FIFO/device/socket)
call_next(context)
end
end
Expand Down Expand Up @@ -144,10 +148,6 @@ class HTTP::StaticFileHandler
%{W/"#{modification_time.to_unix}"}
end

private def modification_time(file_path)
File.info(file_path).modification_time
end

record DirectoryListing, request_path : String, path : String do
def each_entry
Dir.each_child(path) do |entry|
Expand Down