AjaxForm and app engine blobstore
Asked Answered
C

3

16

I'm having some difficulties with AjaxForm file upload and the app engine blobstore. I suspect the difficulty is because the blobstore upload handler (subclass of blobstore_handlers.BlobstoreUploadHandler) mandates a redirect response, rather than returning any content, but I'm not sure. I'm expecting to get an XML document to work with, and it appears to arrive as expected to the browser, but I just can't get hold of it - details below.

My app engine blobstore upload handler is as follows -

class UploadHandler(blobstore_handlers.BlobstoreUploadHandler):
  def post(self):
    upload_files = self.get_uploads('file')  # 'file' is file upload field in the form
    blob_info = upload_files[0]

    entity_key = self.request.get("entityKey")

    // Update a datastore entity with the blobkey (not shown)

    // redirect to the uri for the updated entity
    self.redirect('%s.xml' % entity_key)

The final redirect is to a uri in my application that returns an xml document. Looking at the server output, there is no indication that anything is wrong - the redirect is serviced, and it returns the xml document as expected, with the correct mime type - so the form submission looks good, and the server response to that submission looks good.

My client side code using ajaxForm looks as follows (sorry its a little obtuse, I dont think the problem is here though)-

// Create the form
var dialogForm = $("<form method='POST' enctype='multipart/form-data'>")
   .append("<span>Upload File: </span><input type='file' name='file'/><br>")
   .append("<input type='hidden' name='entityKey' value='" + entityKey + "'/>")
   .append("<input type='hidden' name='entityField' value='image'/>")
   .append("<input type='button' value='Wait...' disabled='disabled'/>");;

dialogForm.ajaxForm();

// Turn the form button into a nice jQuery UI button and add a click handler
$("input[type=button]", dialogForm[0]).button()
   .click(function() {
      log.info("Posting to : " + dialogForm.attr('action'));
      dialogForm.ajaxSubmit({
         success: function(responseText, statusText, xhr, $form) {
            log.info("Response: " + responseText + ", statusText: " + statusText + ", xhr: " + goog.debug.expose(xhr) + ", form:" + goog.debug.expose($form));
         }
      });
    });

I set the 'action' on the form (and enable the button) afterwards -

$.get('/blob_upload_url', function(data) {
  dialogForm.attr("action", data);
  $("input[type=button]", dialogForm[0]).attr("value", "Upload").button("option", "disabled", false);
};

I'm using a little google closure in there as well for logging and exposing objects. Everything looks good - as expected it is posting correctly to the server, and the success function is called. If I watch the document structure in Chrome dev tools, I can see the iFrame being created briefly to handle the file upload and response.

The problem is that I never get the xml document in the response. The log output is as follows -

[ 18.642s] [Panel] Response: null, statusText: success, xhr: 0 = [object HTMLFormElement]
length = 1
selector = 
jquery = 1.4.2, form:0 = [object HTMLFormElement]
length = 1
selector = 
jquery = 1.4.2
Resource interpreted as document but transferred with MIME type application/xml [ABCdefGH]

The complaint by chrome about the mime type is probably super relevant, but I'm not making the connection :) - at least it means that it is getting the xml document at some point. In Chrome resources view, you can see the POST, and that the response is a 302 redirect, and then the subsequent GET request - the header of which looks fine -

Request URL:http://localhost:8081/_ah/upload/ABCdefGH
Request Method:GET
Status Code:200 OK
Request Headers
Referer:http://localhost:8081/
User-Agent:Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_4; en-US) AppleWebKit/533.4 (KHTML, like Gecko) Chrome/5.0.375.70 Safari/533.4
Response Headers
Cache-Control:no-cache
Content-Length:2325
Content-Type:application/xml
Date:Sun, 20 Jun 2010 20:47:39 GMT
Expires:Fri, 01 Jan 1990 00:00:00 GMT
Server:Development/1.0

Chrome resources view won't show me the content of that document (just blank) but firefox does and the xml document looks fine. Firefox gives the same end result however - null for the ajaxSubmit() responseText.

I figure I'm just having a brain fade here somewhere, but it's really got me stumped. Any pointers for getting that xml document would be great - cheers,

Colin

Checkrein answered 20/6, 2010 at 20:59 Comment(5)
I'm guessing that Ajaxform doesn't handle redirects properly - either on its own, or because it's using a flash component that doesn't handle them. Try changing your upload handler to a regular request handler and returning the document directly instead of a redirect, and see if that makes any difference - that'll at least tell you if it's the redirect or not.Leadbelly
Yep ok - confirmed it was redirects as suggested (it doesn't use flash). How come the blobstore UploadHandler mandates a redirect? Anyone got an ajax style upload working with the blobstore? i.e. the user does not experience the redirect or a page refresh? Thx.Checkrein
forum.jquery.com/topic/… - author confirming it can't chase redirect. Seems that the forced redirect behaviour is overkill with the blobstore API?Checkrein
<code> success: function(responseText, statusText, xhr, $form) { log.info("Response: " + responseText + ", statusText: " + statusText + ", xhr: " + goog.debug.expose(xhr) + ", form:" + goog.debug.expose($form)); } </code> This looks like it may not work, since it is interpreting the response as xml, I think you have to use jquery to parse the xml and retrieve the data you want to use. e.g. <code> $(responseText).find('tagname').text(); </code>Bainbrudge
responseText is null, not an xml document. This is because the response is a redirect (302) and does not have a body.Checkrein
J
4

Here's a method I've used (only tested in Chrome) slightly modified. It's not AjaxForm but it works.

function upload_files(entityKey, files, url, progress_callback) {
  var xhr = new XMLHttpRequest(), formData = new FormData();
  xhr.upload['onprogress'] = progress_callback;

  formData.append('entityKey', entityKey);
  $.each(files, function(i, file) { formData.append('file[]', file);});

  xhr.open("post", url, true);
  xhr.setRequestHeader("Cache-Control", "no-cache");
  xhr.send(formData);
}

The entityKey is available as a param on the server. The 'files' parameter comes from the 'files' attribute of the file-type input form element (as an array to support multiple). The 'progress_callback' parameter is a function that takes an object that has (at least) a 'loaded' and a 'total' field (unit is bytes). It doesn't care about the server response.

Janey answered 2/12, 2010 at 11:53 Comment(1)
Ok, so instead of waiting for the response, use the progress callback to identify when the upload has completed (loaded == total?), and then query for the updated entity separately - as you say, completely ignore the server response? Ta.Checkrein
S
1

here is how i solved it. I added a random id generatated in javascript send along with file. once the upload complete, configure my server to remember the association of that random id and uploaded file for a while. I send another query to a predefine url like mysite.com/blobdata/that_random_id_i_renerated to request the file just uploaded. it worked.

Seanseana answered 28/3, 2011 at 12:43 Comment(0)
S
0

If you are 5 months stuck on the same problem.. I think you should ask here:

http://www.google.com/support/forum/p/Chrome/

Sienna answered 24/11, 2010 at 8:22 Comment(1)
This problem is almost certainly not a chrome problem - it occurs in the other browsers. The problem, depending on how you look at it, is either the blobstore api mandating a redirect, or the AjaxForm not chasing that redirect. In the absence of any justification from google I lean towards the former - seems like they've created a hoop to jump through for no reason.Checkrein

© 2022 - 2024 — McMap. All rights reserved.