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

Handling multipart requests #368

Closed
wants to merge 17 commits into from
Closed

Conversation

bazi
Copy link
Contributor

@bazi bazi commented Jun 18, 2015

First of all let me note that this PR is not complete yet and needs community's feedback.

What this PR targets

Parameter injection into controller methods does not work yet with multipart/form-data requests. This issue was addressed in mail group here where @raphaelbauer proposed to make @Param("...") String param work with multipart requests and additionally introduce something like @File("...") File file that directly injects uploaded file into controller's method.
Besides, i found an old issue #88 that roughly says to hide commons-upload behind some Ninja API.
Well, this PR is all about those issues.

What's already done

Almost everything to handle multipart requests. There are new annotations @FileParam and @FileParams that can be applied to controller method's parameters. For instance, by adding @FileParam("myfile") File file into your controller method you have a ready java.io.File instance to read uploaded file contents from.
Additionally, every form parameter of a multipart request (i.e. those that are not attached files) can be injected by @Param annotation.

What's not done, issues

Cleanup of uploaded files

When multipart request are parsed, uploaded files are locally saved in temp directory. I need your feedback on how to clean up those files.

  • First thing that comes to my mind is to use finalize method of Context implementation(references to files are stored in Context).
  • Or there may be some aggregator of temporary files which deletes them after some time (thinking of a scheduler).

What are your suggestions? Can we leave cleanup responsibility to application developers or app administrators? I guess not :)

Commons-upload

Well there are no issues with the library ;) This is more related to issue #88.
Currently to handle file uploads we use Context.getFileItemIterator() which returns us an iterator of multipart items aka parts. This is the recommended way of handling parts, it is described in Streaming API. As its name implies parts are streamed and you have to handle them once. In this PR, multipart requests are parsed automatically (i.e. iterator is used) and all related parameters and files are available for injection. So Context.getFileItemIterator() becomes useless -- it won't anymore return an iterator that you can iterate through parts.
I thought to put @Deprecated on that method but deprecated methods shall have their old behavior for migration period but in our case that won't be the case. How hard would migration be if that method is removed?

@buildhive
Copy link

Ninja Web Framework » ninja #630 FAILURE
Looks like there's a problem with this pull request
(what's this?)

@buildhive
Copy link

Ninja Web Framework » ninja #631 SUCCESS
This pull request looks good
(what's this?)

}
}
} catch (FileUploadException | IOException ex) {
logger.error("Failed to parse multipart request data", ex);
Copy link
Contributor

Choose a reason for hiding this comment

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

Hmm... should we throw the error? So we can fail fast - or just silently ignore that parsing went wrong and log... not 100% sure...

@raphaelbauer
Copy link
Contributor

That looks very nice!

Obvious stuff missing:

  1. Unit tests for the classes...
  2. Documentation how stuff works
  3. changelog.md ofc

Stuff that we still need to tackle:
I personally run Ninja on systems that do not allow to store uploaded files on the file system (no temp space). So I'd need an "in memory" mode to handle multipart requests. Can you think of a nice way how to disable the "automatically-store-multipart-files-on-temp-disk" thing?

Stuff you asked:
Regarding cleanup of temp files. finalize() sounds a bit too weak as we have no guarantee that finalize() is called at all. What about deleting the temp files once the result returns to ResultHandler? We could use a finally {} block to do that... We must then also add documentation - if someone wants to use the file after the controller method exists - she has to make sure that file got copied somewhere else...

Hope that helps :)

Your PR looks really nice. The uploading facilities were really not a nice part of Ninja. But you improved it very very nicely :) Good work!

@buildhive
Copy link

Ninja Web Framework » ninja #643 FAILURE
Looks like there's a problem with this pull request
(what's this?)

@buildhive
Copy link

Ninja Web Framework » ninja #644 FAILURE
Looks like there's a problem with this pull request
(what's this?)

@buildhive
Copy link

Ninja Web Framework » ninja #645 FAILURE
Looks like there's a problem with this pull request
(what's this?)

…art-params

Conflicts:
	ninja-core/src/site/markdown/developer/changelog.md
@bazi
Copy link
Contributor Author

bazi commented Jun 24, 2015

Updates:

  • added full in-memory file upload handling (added commons-upload interface implementations), activated in application config
  • clean up of temporary files (handled in ResultHandler as @raphaelbauer suggested)
  • docs, tests, changelog, ...

P.S.
Context.getFileItemIterator() is still usable even if file item iterator is used to parse multipart request payload. Further calls are simulated by returning custom implementation of iterator.

@buildhive
Copy link

Ninja Web Framework » ninja #646 SUCCESS
This pull request looks good
(what's this?)

@buildhive
Copy link

Ninja Web Framework » ninja #647 SUCCESS
This pull request looks good
(what's this?)

@raphaelbauer
Copy link
Contributor

This looks really good - unfortunately we are quite busy right now - so it may take some more days to review and merge :)

Conflicts:
	ninja-core/src/site/markdown/developer/changelog.md
@buildhive
Copy link

Ninja Web Framework » ninja #665 SUCCESS
This pull request looks good
(what's this?)

@buildhive
Copy link

Ninja Web Framework » ninja #666 SUCCESS
This pull request looks good
(what's this?)

@raphaelbauer
Copy link
Contributor

Looking now into the PR. Quite a lot of code... So reviewing may take a while :)

StringBuilder sb = new StringBuilder();
try (InputStreamReader isr = new InputStreamReader(fileItemStream.openStream())) {
int n;
char[] buf = new char[128];
Copy link
Contributor

Choose a reason for hiding this comment

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

can be replaced by a simple Streams.asString(item.openStream());

@raphaelbauer
Copy link
Contributor

before we continue a deeper discussion. Do you think it is possible to incorporate MultipartContextImpl into ContextImpl?

That you'd be the first thing I'd try to do - to remove a lot of code and also remove the bridge that checks whether to create a MultipartContextImpl or a ContextImpl.

Providing the InputStream feels handy to me... :)

@buildhive
Copy link

Ninja Web Framework » ninja #683 SUCCESS
This pull request looks good
(what's this?)

bazi added 2 commits July 22, 2015 10:47
Conflicts:
	ninja-core/src/site/markdown/developer/changelog.md
@buildhive
Copy link

Ninja Web Framework » ninja #697 SUCCESS
This pull request looks good
(what's this?)

@raphaelbauer
Copy link
Contributor

I don't care. We can merge this PR first and then @momiji can submit the improved version.
@momiji Ninja is released 12 times a year roughly.

@bazi I got some more remarks (tiny stuff I did not say until the big things were ironed out...) I'll add that now... Once that's incorporated we can merge it into master... :)

@@ -41,6 +41,8 @@
interface Impl extends Context {

void setRoute(Route route);

void cleanup();
Copy link
Contributor

Choose a reason for hiding this comment

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

maybe we can rename that to cleanupAfterRequest() + add one line of javadoc...

@raphaelbauer
Copy link
Contributor

Okay finished adding my smaller remarks. A lot of formatting and language things :) Don't take it personally - it's really just to make things better and keep things uniform across the codebase...

@raphaelbauer
Copy link
Contributor

@bazi Let us know when you are finished - then we can merge the PR :)

@momiji
Copy link

momiji commented Jul 23, 2015

I doubt you can merge my PR after, as it use a very different approach than this one. It will require to remove too many things from this PR, and should be considered as a drop-in replacement.

Making my own PR was more a challenge to see if the "file provider" was something that could be done, and I found that it was quite easy to implement. It also make me learn things about Github branching, Mockito tests, Guice injection, and so on.

The important thing is to have a way to handle files, whatever way is used to do it, and to do it in a way that let's the final developers do their job, with enough flexibility.

I would have only one request before merging this PR, please be sure you do not break getFileItemIterator() current behavior. It may already be used in some applications, and shod still work as before, meaning providing original streams to the developer.

I'm just saying that because we are already using it in several 3 different ways:

  • save file to disk
  • save file to disk with on-the-fly encryption
  • save file chunks to disk (for very big files) with on-the-fly encryption

I doubt to be able to do all that with the new way to handle uploads, as we have both in-memory and disk, but this is not a problem as long as we can still use the getFileItemIterator(), as actually done.

If not, then this should be mentioned that this is a breaking change, and we will switch to use the internal httpServletRequest and ServletFileUpload, as it was originally coded.

@raphaelbauer
Copy link
Contributor

Ok - how should we proceed? I don't care how it is implemented as the way it is currently is very very low level (but works of course). Any improvement would be great.

The only constraint I have: I want to be able to control whether the file is stored in memory or on disk.

Anything else can be discussed :)

I guess it is about you two: @bazi @momiji - you did the great job of actually implementing great solutions. Can you agree on one? Or agree on how we want to proceed from those two PRs? Thanks :)

@bazi
Copy link
Contributor Author

bazi commented Jul 23, 2015

@momiji getFileItemIterator() is marked deprecated but it surely keeps its behavior.

Anyways, i made a decision. Let's abandon this PR and focus on the other one from @momiji, guess it is #383 but it seems closed. Please provide PR, I will be on the topic.

@raphaelbauer beg your pardon for your time spent on reviewing ;-)

@raphaelbauer
Copy link
Contributor

Absolutely no problem! It's a pleasure to read code and improve things together! :)
Let me know when I can help on the "other" PR... As I said above - it would be great if you two could team up to implement the feature!

Cool effort!

I'll close this PR then...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants