Monthly Archives: January 2013

CORS support in 3.3

Updates since the original post/feature implementation

After receiving a number of requests during a relatively short amount of time, I decided to add support for Cross Origin Resource Sharing (CORS). This allows you to send requests to domain A and return the response from some other domain. You will need to enable this new feature if the domain of the server returning responses to Fine Uploader does not match the domain of the origin server. If you don’t know what this means, please do not enable CORS support. Support exists for both upload requests and delete file requests.

Support exceptions

  • CORS requests in IE7 are not supported. In IE8 and IE9, the response from the request iframe is passed to the uploader window via window.postMessage, which is not supported in IE7.
  • Delete file CORS requests in IE9 and earlier are not supported. This is due to the fact that DELETE requests must be preflighted. IE9 and earlier require XDomainRequest to be used when sending CORS requests, and XDomainRequest does not support preflighting. The delete files feature will be automatically disabled in IE9 and earlier if Fine Uploader CORS support is enabled.
var uploader = new qq.FineUploader({
    element: document.getElementById('myUploader'),
    request: {
        endpoint: 'server/upload'
    }
    cors: {
        //all requests are expected to be cross-domain requests
        expected: true,

        //if you want cookies to be sent along with the request
        sendCredentials: true
    }
});

Enabling CORS support

If you set the expected property to true, it is assumed that all requests will be cross-domain requests. If you set expected to true and do not respond using the cross-domain response convention outlined below for non-XHR requests, even if the request is NOT cross-domain, it will be considered a failure.

Handling XHR CORS requests server-side

An incoming upload request was sent via XMLHttpRequest if the request has an X-Requested-With header with a value of “XMLHttpRequest”. If CORS support is enabled in Fine Uploader, you must properly handle the associated requests and format your responses accordingly. Note that, since non-standards headers are sent with all Fine Uploader requests, ALL XHR requests are preflighted. This applies to both delete file requests (which are all XHR requests) and upload requests on browsers that support the File API. This means that you will need to handle an OPTIONS request as well. Your responses must include the appropriate Access-Control headers. If you set the sendCredentials property to true, you should be aware that your responses must NOT include wildcard Access-Control-Allow-Origin headers, and you must also include the Access-Control-Allow-Credentials header. The actual response text for CORS XHR requests will not differ at all from response text for non-CORS requests.

Handling iframe CORS upload requests server-side

An incoming upload request was sent via a form submission inside of a hidden iframe if the request does not have an X-Requested-With header, or if the X-Requested-With header has a value other than “XMLHttpRequest”. Handling cross-domain iframe/form-submit-initiated uploads (in browsers that do not support the File API) is a bit tricky for Fine Uploader. I’ve done my best to make it as simple as possible for developers/integrators, though. Your response for iframe based request (such as required when using IE8 or IE9), will look very similar to your normal response, except it must have a Content-Type header value of “text/html”, and your response must import a helper javascript file via a <script> tag immediately preceding your valid JSON response. For example:

"{\"success\": true, \"uuid\": \"9da17ad5-ad6a-40cd-81b5-226e837db45b\"}<script src=\"http://<YOUR_SERVER_DOMAIN>/iframe.xss.response-<VERSION>.js/"></script>"

All cross-domain iframe-initiated responses must end with the script tag above, and the valid JSON portion MUST include the UUID of the associated file. Note that you must host the iframe.xss.response.js file on an accessible server and reference it in the &ltscript&gt tag accordingly. When the contents of your response is returned to the iframe tracked by Fine Uploader, the associated javascript file imported by the script tag above will be executed, and will pass the JSON portion of your response to the uploader via window.postMessage.

Additional reading on CORS

If you are unfamiliar with handling XHR CORS requests, Mozilla Developer Network has an excellent wiki article on CORS. Please pay attention, in particular, to the sections that describe how to handle preflighted requests, and credentialed requests (if you require credentials to be passed with the cross-domain request and you have set the sendCredentials cors property).

This is my first stab at CORS support in Fine Uploader. So, please let me know if anything can be improved.

-Ray Nicholus

Delete an uploaded file in 3.3

Updates since the original post/feature implementation

Fine Uploader 3.3 has its first new feature: Delete an uploaded file. This has been a highly requested feature, and it’s finally available in 3.3. This feature, if enabled, will allow your users to delete files after they have been uploaded. There are appropriate UI elements for FineUploader mode users (a customizable delete link, status indicators, an optional and customizable confirm dialog) along with a new API function for FineUploaderBasic mode users (and FineUploader mode users), along with a few new callbacks.

Overview

A file is eligible for deletion only after it has been successfully uploaded. In FineUploader mode, if this feature is enabled, a customizable delete link will appear next to the file after it has successfully uploaded.

When the user clicks on this link, first, the onSubmitDelete callback will be invoked. You may return false in your callback handler if you want to abort the request. If not the user will see a (customizable) confirm dialog (if forceConfirm property has been set). If they click “ok” (or if the forceConfirm option has been disabled), a DELETE request will be sent to the server after Fine Uploader invokes the onDelete callback.

A status message, along with a spinner, will appear next to the file while Fine Uploader is waiting for the server response. Once the response is received, Fine Uploader will invoke the onDeleteComplete callback. If the server indicates success in its response, the file will be removed from the UI. If the server indicates failure, a failure status message will be displayed next to the file. If an error has been detected in the response, the onError callback will also be invoked.

In FineUploaderBasic mode, you can order a delete request via the new deleteFile API function. All of the callbacks invoked while in FineUploader mode will also be invoked in FineUploaderBasic mode if you order a delete via this API function.

The Request (and server-side handling)

Fine Uploader sends a separate DELETE request for each file marked for deletion. The UUID of the file is passed as the last item in the request URI. For example, if the UUID of a file, ordered for deletion, is “6a9955d3-8f1e-4c70-ae47-b01b35dd1562”, and the endpoint property specified in the deleteFile option object is “/server/upload” and the domain is http://mysite.com, Fine Uploader will send a DELETE request to the following endpoint:

http://mysite.com/server/upload/6a9955d3-8f1e-4c70-ae47-b01b35dd1562.

The W3C spec specifically states that the resource to be deleted must be part of the URI path, similar to a PUT request.

Note that you can specify custom parameter and additional headers that are sent along with this request. If you specify custom parameters, they will be sent as part of the query string, NOT the request payload. This is due to the fact that many servers will either rip out any request body associated with a DELETE request, or simply throw an error/reject the request. In other words, a DELETE request is much like a GET request.

Note that I have already updated the Java server-side example to handle this new feature, and the PHP file will likely be updated as well before 3.3 releases.

The (Expected) Response

You may return any response you like, as the XMLHttpRequest object will be passed to your onDeleteComplete handler for further examination. However, the response code you return is important to Fine Uploader, as it uses this to determine if the delete has succeeded or not. The following response codes indicate success for this specific request: 200, 202, and 204. Any other response code indicates a failure.

Enabling the Feature

The following is an example of a VERY basic setup that enables you to use the new delete files feature. For more info on the available options, please see the readme.

var uploader = new qq.FineUploader({
    element: document.getElementById('myUploader'),
    request: {
        endpoint: 'server/upload'
    }
    deleteFile: {
        enabled: true,
        forceConfirm: true,
        endpoint: 'server/file'
    }
});

As always, please let me know via the issue tracker or the support forum if you have any ideas that may improve this or any other feature.

-Ray Nicholus

Fine Uploader 3.2

Fine Uploader 3.2 has been released, and I have done my best to include a bunch of improvements, along with some new features. The two new major features are file chunking and auto-resume.

As always, please see the downloads page to download the library.

Known issues

  • #595 – inputName parameter is included in both the query string AND the request payload of XHR requests if forceMultipart is true (default) and paramsInBody is false (default). This will be fixed in 3.3. This will NOT affect most users. If it does negatively impact you, an easy workaround is to set the paramsInBody property of the request option to true. Note that in 3.3, the paramsInBody default value will be changed to true anyway. Please read more about this option in the options, and in the server-side readme.
  • #584 – “Processing…” status message does not appear while waiting for response after sending last byte of last chunk to server. This only affects FineUploader mode. I plan to address this in 3.3. Not a major issue, but it deserves to be addressed. Please see the FAQ in the readme for more information about existing inconsistencies among browsers as far as this “Processing…” message is concerned.

Features & Enhancements

  • #377 – Add support for optional file chunking. I have explained how this works, in detail, in a blog post.
  • #530 – Allow users to resume failed/interrupted uploads from previous sessions. I have explained how this works, in detail, in a blog post.
  • #575 – Add a qqtotalfilesize parameter to FormData multipart encoded requests so server-side code can easily determine the expected file size.
  • #569 – Added an API function to retrieve File object given an ID.
  • #566 – Add an API function that returns the size of a file, given the file’s ID.
  • #546 – Version-stamp the CSS file contained in the released ZIP file.
  • #541 – Allow developers to easily override the logic used to display the file name in FineUploader mode by contributing a formatFileName function option during initialization.
  • #509 – Allow developers to change the endpoint at any time via a new setEndpoint function. The concept for this is the same as the setParams function.
  • #111 – Ensure allowedExtensions validation check handles complex extensions, such as tar.gz, correctly.
  • #63 – Allow developers to easily localize the size symbols (MB, GB, etc) via a new sizeSymbols option.

Bugs Fixed

  • #568 – onValidate is called too many times in browsers that do not support the File API. This fix also resulted in a breaking change to simplify the new “custom validators” feature. Please see the breaking changes section below for more details.
  • #567 – FileData objects passed into the onValidate callback were sometimes File objects instead.
  • #565 – “Upload Failed” message remains during manual retry attempt.
  • #574 – onLeave message appears after canceling in-progress upload w/ autoUpload set to false.
  • #562 – Processing graphic remains and dropping is no longer possible after attempting to drop multiple files w/ multiple option set to false. Thanks to twig for reporting this.
  • #548 – default implementation of showMessage causes Safari to hang on IOS6. Thanks to turntreesolutions for reporting this.

Breaking Changes

I introduced a new callback: onValidateBatch that takes an array of FileData objects and is called once with all files selected. The onValidate callback will then always contain a FileData object and will be called for each of the files selected in the batch. This should simplify the new custom validation feature a bit. Please see the callback entries in the readme for more details.

Also, forceMultipart now defaults to “true”. This really should not be a breaking change, as your server-side code should already properly handle multipart encoded requests.

Note About The Codebase

I continue to refactor the codebase in order to make it more maintainable and more JSHint compliant. I recently switched from JSLint to JSHint, as I have found JSHint to be much more practical for real-world applications due to its flexibility.

Major Features Planned For 3.3?

  • Copy and paste image upload. See #487 for more details.
  • Provide an optional delete button next to each file item (in FineUploader mode) that will send a DELETE request to the server. I may provide an API function for FineUploaderBasic users as well. See #382 for more details.

Important note about 3.3

I plan to set change the default value of the paramsInBody option to true. I suspect that most developers expect parameters of multipart encoded requests to be located in the request body/payload. This should, over time, reduce some of the confusion I have seen in the support forums regarding request parameters.

If you have a question or a suggestion, please use the support forums or the issue tracker. Questions or issue reports will not be addressed in the comments section below.

As always, please let me know (in the forums or the issue tracker) if you have any suggestions for improvement, or any killer features you’d like me to add.

-Ray Nicholus

Resume uploads from previous sessions in 3.2

Suppose you’re sitting in a coffee shop, slowly uploading a very large file. Your lunch break is over and you have to head back to the office, but your upload is no where near complete. In version 3.2 of Fine Uploader, you can simply close your browser, head back to the office, and re-select or drop the file back into the uploader. It will pick up where you left off. Perhaps you are uploading another large file, but your PC blue-screens in the middle of the upload. Once you get your browser back up and running again, simply drop or select the file again and Fine Uploader will resume your file upload. The uses for such a feature are many.

High-level summary

The ability to resume an upload is dependent upon the 2nd-newest feature of Fine Uploader 3.2: file chunking. Before each chunk is sent to the server via a POST request, Fine Uploader creates a persistent cookie with all of the information required to resume the upload. This is done to cover termination of the browser session before the chunk has been successfully received by the server. After Fine Uploader has confirmed that the chunk has been successfully processed, the cookie is either deleted (if there are no more chunks left for this file) or updated with the metadata for the next chunk.

File resume is supported on all browsers the currently support chunking. That is: IOS6, Chrome, Firefox, Safari for Mac, and Internet Explorer 10. Again, file chunking, and therefore resume, is not supported in Android due to a bug in Android’s implementation of Blob.prototype.slice().

Configuring

I have provided the ability to enable or disable the resume feature (it’s disabled by default). Also, the number of days a resume cookie can live is configurable, but defaults to 7 days. Finally, you may specify an ID property that will be used to further distinguish resume cookies stored by the uploader. You may find this useful, if, for instance, you would like to tie resumable files to a specific user.

Note that you must also enable the chunking feature if you want to use resume. The qQuery section of the code now has some more general purpose functions used by the internal resume feature. These general purpose functions allow you to easily create, get, and delete cookies.

Callbacks

I’ve also provided a callback, onResume, with some useful parameters, that is invoked before a resume begins. The file ID, along with the name of the file and some data specific to the chunk to be sent are passed to the callback. If you want to abort the resume attempt client-side and simply start uploading from the first chunk, you can return false in your callback handler. See the callbacks section of the readme for more details.

API

I have also added a new method to the API: getResumableFilesData. This allows you to obtain a list of files that are resumable in the current session. You may find this useful if you want to display a message to the user after the uploader is initialized. Please see the instance methods section of the readme for more details on this function.

Server-side support

On the server side, there is very little you have to do if you are already accounting for chunked uploads. You can determine when a resume has been ordered by looking for a “qqresume” parameter in the request with a value of true. This parameter will be sent with the first request of the resume attempt.

It is important that you keep chunks around on the server until either the entire file has been uploaded and all chunks have been merged, or until the number of days specified in the `cookiesExpireIn` property of the resume option have passed. If, for some reason, you receive a request that indicates a resume has been ordered, and one or more of the previously uploaded chunks is missing or invalid, you can return a valid JSON response containing a “reset” property with a value of “true”. This will let Fine Uploader know that it should start the file upload from the first chunk instead of the last failed chunk.

Basic client-side example

It’s really quite simple to start using the new resume feature, here’s the simplest example I could come up with:

var uploader = new qq.FineUploader({
    element: document.getElementById('myUploader'),
    request: {
        endpoint: '/my/endpoint'
    },
    chunking: {
        enabled: true
    },
    resume: {
        enabled: true
    }
});

Since file upload resume is a new feature, I’m interested to hear any ideas from users who would like to make this feature even more useful. As always, if you have any input or discover any bugs, feel free to file an issue.

-Ray Nicholus