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
AppFramework StreamResponse #13616
AppFramework StreamResponse #13616
Conversation
nice - THX @Raydiation |
* Streams the file using readfile | ||
*/ | ||
public function callback () { | ||
@readfile($this->filepath); |
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 somewhat wondering whether it would make sense to throw an exception in case an error happened (i.e. readfile
returned false
)
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.
We might also want to evaluate XSendFile support for this one
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.
Good question, I actually don't know because there is nothing that catches the exception. ATM it will just silently fail if the content is not available which is not a good idea i think. Just throw any exception?
Ideally we'd have a 404 if the file does not exist, the question is just where and how we should throw the exception. Maybe it would be best to just leave this up to the developer, e.g.:
public function controllerMethod($fileId) {
try {
$filePath = $this->service->findPath($fileId);
return new StreamResponse($filePath);
} catch (DoesNotExistException $e) {
return new Response(Http::STATUS_NOT_FOUND);
}
}
otherwise we'd need custom middleware that checks the filepaths of all StreamResponses and replaces it with 404 responses which is akward I think.
As for xsendfile: a good idea but how do we handle php streams? How would we return a file that is on an FTP? It is fast but probably has a different use case because of the imposed limits and should go into a separate response that falls back to readfile if xsendfile does not exist
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.
+1 for the exception which has to be catched by the single dev.
It is fast but probably has a different use case because of the imposed limits and should go into a separate response that falls back to readfile if xsendfile does not exist
Agreed as well.
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.
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.
Yep should be upper-case p
Tests are added by using a very simple wrapper around print/header/setcookie |
The only thing im still unhappy about is the naming |
} | ||
|
||
|
||
} |
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.
Newline at EoF
ab7b845
to
b716c3b
Compare
I fixed the code style issues as well as the issue with the wrong variable and squashed all commits. |
@LukasReschke thanks! |
Makes sense - works 👍 @MorrisJobke @nickvergessen Second review? |
By the way, I'm wondering if it would make sense to forbid directory traversals (e.g. What do you think, @Raydiation? |
It makes sense to require a realpath (as in no ../../ in it) but then people will probably just work around that using realpath(). It may be easier to restrict it to certain paths. Maybe data and apps/appid/data? We can always change that by passing additional constructor parameters to change the behavior, as in whitelist more directories |
That would probably be a good idea. I'd say it's enough to go for |
/** | ||
* Very thin wrapper class to make output testable | ||
*/ | ||
class IO { |
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.
OC IO, kappa 🙊
I'd prefer meaningful names, although IO might be known, InputOutput doesn't hurt either?
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.
IO is very common, no need to fully write it, InputOutput on the other hand sounds weird.
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.
Seems the class is only doing output, so I'm going to rename it to Output
Didn't test, but looks good to me |
Does this work with all supported resources? Encrypted files, S3, SMB, NFS, external cloud, etc. ? As a concrete example, I would use the exact same class, but extend StreamResponse, modify the ctor and pass in a path instead of a string. |
@oparoz if it supports streams, yes it will work. You can simply inherit from StreamResponse and set the headers in the constructor and pass the filepath to the parent constructor. Depending on your needs you may want to use mod xsendfile though since serving the file with PHP is painfully slow. You can do that by implementing ICallbackResponse and setting the appropriate headers and send a PR if you are interested. |
@Raydiation - Thanks. I'll give it a try once this is merged then. I probably won't use mod xsendfile though as pictures are not that big.
Will it fail gracefully if the resource is of the wrong type? To me the AppFramework is an abstraction layer. I don't want to have to think about where the resource is located. I ask for a file, I'm given a path, I send it to the browser. |
Fail gracefully as in wont output anything ;) |
Hmmm ;) |
Ok, updated the PR:
|
} | ||
|
||
/** | ||
* @param string $path |
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.
Intendation is a bit shifted
// handle caching | ||
if ($output->getHttpResponseCode() !== Http::STATUS_NOT_MODIFIED) { | ||
if (!file_exists($this->filePath)) { | ||
$output->setHttpResponseCode(HTTP::STATUS_NOT_FOUND); |
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.
UPPERCASE vs Capitalcase?
Looks good beside my comments |
a355128
to
d0852d7
Compare
@@ -24,6 +24,10 @@ | |||
|
|||
namespace OC\AppFramework; | |||
|
|||
use OCP\AppFramework\Http\Response; | |||
use OCP\AppFramework\Http\IO; |
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.
Is this really needed? And then it's misspelled;)
Refer to this link for build results (access rights to CI server needed): |
Squash? 🙈 – 6 commits that say "Fix indention" are not really helpful 🙈 |
Will squash once there are two thumbs up ;) |
Works 👍 |
First stab at the StreamResponse, see #12988 The idea is to use an interface ICallbackResponse (I'm not 100% happy with the name yet, suggestions?) that allow the response to output things in its own way, for instance stream the file using readfile Unittests are atm lacking, plan is to check if a mock of ICallbackResponse will be used by calling its callback (also unhappy with this name) method Usage is: $response = new StreamResponse('path/to/file'); rename io to output, add additional methods and handle error and not modified cases when using StreamResponse fix indention and uppercasing, also handle forbidden cases fix indention fix indention no forbidden, figuring out if a file is really readable is too complicated to get to work across OSes and streams remove useless import remove useless import fix intendation
cc60db1
to
95239ad
Compare
AppFramework StreamResponse
The inspection completed: 1 new issues, 18 updated code elements |
1 similar comment
The inspection completed: 1 new issues, 18 updated code elements |
First stab at the StreamResponse, see #12988
The idea is to use an interface ICallbackResponse (I'm not 100% happy with the name yet, suggestions?) that allow the response to output things in its own way, for instance stream the file using readfile
Unittests are atm lacking, plan is to
Usage is:
@DeepDiver1975 @MorrisJobke @LukasReschke @PVince81 @oparoz