Back to the Future
I previously said I had implemented an FTP based solution for file upload. Well I did and it works pretty well but it's a bit fiddly and tricky to get working so I kept looking for solutions. I found one. Hooray! Flex 4 and FileReference in Flash 10 with Blaze provide the solution.
A big irritation of mine with http uploads using multi part form stuff is that it is an all or nothing proposal. Once you fire up the connection and start uploading you have to wait for the whole thing to go up. For small files this isn't such a problem but if you upload a file that takes a minute or two you often don't know if it is working (from a client perspective) until you decide that 3 hours is too long for a file to take. There are heaps of issues like socket disconnected by peer, socket read timeout and so forth. To get things working you need to check special upload timeouts and browser timeouts and virus checkers / indexers on the server and other mysterious Mac OS X related muffery. To use file upload over http in a true enterprise application you need some guarantees that the upload will happen and that data isn't lost. You need a reliable system so you can tell your paying customers that their data will get to the server and if it doesn't you know what happened and you can fix it and keep going. Anything less is amateur hour mickey mouse stuff.
Flash 10 FileReference
Flash player 10 allows you to call fileReference.load() to load a selected file. This puts all the file's bytes into the data property. You can then send that as a byte through AMF via Blaze to the server where you can save the file. The problem with this simplistic approach is you don't get any progress events and if it is a big file you still end up with all the problems of http upload. The solution is really simple; break the byte array into manageable chunks and send them up one at a time waiting for a response between each one. This way you get a progress event and the system doesn't just freeze up until it's finished. If you are clever you can also enable pause / resume.
Yeah well so my mother says anyway. But I wrote a nice little flex library and java library which go together to provide file upload services. The flex part takes a byteArray and file name and queries the java service to see what progress the file is up to. The java service replies with a progress event which either says 0 bytes uploaded, some bytes uploaded or complete. You can use that to initialise your progress bar in flex. Then the flex library moves to the correct position in the byteArray and sends a chunk up to java which is written to file and a response sent with progress info. This continues until the file is complete at which point the flex library dispatches a complete event and you can do whatever you like with the file. The java service has some methods that let you get an input stream to the uploaded file and also delete the file once your done with it. If the user cancels midway through the upload (either on purpose or due to error) they can re-upload the file and it will resume from where you left off. Errors are also communicated back to the flex client so you can auto retry or do whatever. Hopefully I'll attach this stuff to my blog. If you like it or find bugs (you probably will) then let me know.
add the jar file to your lib folder
include the swc in your flex project
fileupload.xml goes in your web root or docroot whatever you call it
applicationContext-upload.xml is a spring context file so put it where you load them from
If you don't use spring you should, but if you don't, you can just create a facade that adapts the upload service and creates an instance and whatever. Hey you're at least as clever as me so either use spring or take 5 minutes to figure out a way around it (I did for another project that used ejb)
in your remoting-config.xml
You'll see I'm using the spring factory, if you don't have it do a google search for spring factory for blaze there's also an ejb factory if you are that way inclined.
The fileupload.xml has 2 things in it. The first is the name of the destination you just configured in the remoting-config.xml and the other is the endpoint context path for your channel of choice.
Now to use it in your code:
1. Create an instance of the au.com.reignite.upload.FileUploadManager and remember to declare it in a place that will have scope for the duration of your upload. Same as FileReference below. You need to pass in some start up arguments when creating FileUploadManager: url to fileupload.xml, securityToken which you wont use so hand in null and repositoryId which is a unique identifier for this user's uploads. I use user id for this so the same user can resume their upload. If you don't use a different id for each user then you'll get stuffed up files if more than one user uploads the same file at the same time.
2. create a FileReference and get the user to select a file. Once selected call load(). Once complete you can do the rest.
3. Once the file reference has done its load and you have the data do this:
These are the events that get thrown so you had better handle them. Pretty self explanatory really.
4. call uploadManager.upload(fileRef.data, fileRef.name); which starts it all off. You'll get at least one progress event and a maximum of one complete event.
IF a user cancels or there is an error and you want to start over make sure you call uploadManager.cancel() otherwise you'll break it. So that means if they close the popup your doing the upload in also make sure you call cancel otherwise the upload continues.
Maybe if I get bored I'll post an example app.