-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Split StaticFileHandler#call
into structured components
#15678
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
base: master
Are you sure you want to change the base?
Split StaticFileHandler#call
into structured components
#15678
Conversation
false | ||
end | ||
|
||
private def check_request_path!(context : Server::Context, request_path : String) : Bool |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion: I'd expect a bang method to raise an exception, not return a boolean. Maybe a name such as #allowed_request_path?
would be more explicit to the intent? Then we'd have nice guards:
return unless allowed_request_method?(context)
return unless allowed_request_path?(context, request_path)
return unless valid_redirect_to_expanded_path?(context, request_path, expanded_path, file_info)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't like that because these methods are not just predicates. They directly affect the behaviour (e.g. the HTTP response) in case of an error.
The purpose of the exclamation mark is to indicate that the method handles errors itself and just returns a boolean that indicates whether the caller should continue or stop execution.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not fond of the extracted methods still having some implicit expectations and intermixed behavior: validation + action + implicit out-of-scope control-flow. Maybe split again? Granted, it would be much more verbose but the helpers would do one thing.
Note: it could be interesting to wrap it the context + methods into a maybe not, a struct would mean it's hard to customize, and a class would mean an allocation (but maybe not a bad idea).struct
, and then we could have a nice public API!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The combination of validation + action forms a self-contained component. The only tricky thing about it is handling control flow in the caller scope.
If we want more granular components, we can split the bodies of the check_
methods into separate helpers, of course. But I don't see much benefit from that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be better if we pronounce the action more in the method name?
check_request_method! || return
->respond_on_invalid_request_method && return
check_request_path! || return
->respond_on_invalid_request_path && return
check_redirect_to_expanded_path! || return
->redirect_to_expanded_path && return
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe? I still prefer braindead flow-control, and do one thing helpers, but it's prolly just my taste.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just to be clear, you mean something like this:
unless allowed_request_method?(context)
return redirect_invalid_request_method(context)
end
unless allowed_request_path?(context, request_path)
return redirect_invalid_request_path(context, request_path)
end
# EDIT: This has been refactored, see following comment.
# if expanded_path_needs_redirect?(context, request_path, expanded_path, file_info)
# return redirect_to_normalized_path(context, request_path, expanded_path, file_info)
# end
Especially the latter one would be a bit unfortunate because the redirect needs to check the same conditions again as the validation.
But that's similar for the other as well. The action and condition are strongly connected together. For example, both must consider the same request methods as allowed. If separate methods can be overridden individually, they can get out of sync.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suppose the path redirect could easily be refactored to this:
if normalized_path = normalize_request_path(context, request_path, expanded_path, file_info)
return redirect_to context, normalized_path
end
EDIT: Amended in afe80ec
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You may just want to configure the accepted HTTP methods or paths, or overload the checks and call previous_def
, but you may also just want to render a custom response, independently from the checks.
|
||
return call_next(context) unless file_info | ||
|
||
context.response.headers["Accept-Ranges"] = "bytes" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
issue: I'm not sure what to do about this. It doesn't make sense to set this header always. Some implementations might not support ranges for serving files (Kemal, for example, currently doesn't), and we don't support ranges on directory index pages either.
So this should go somewhere in the serve_file
method, maybe in the branch that doesn't handle ranges, i.e. directly above the call to serve_file_full
?
StaticFileHandler#call
is a complex method with > 100 loc. This patch extracts individual aspects into helper methods which gives it more structure.This also makes it easy for inheriting types to inject partial custom behaviour by overriding individual helper methods (see kemalcr/kemal#714 for an example).