Cuga
Cuga

Reputation: 17904

How to upload file on GAE with GWT?

I've been trying this for hours, so maybe a fresh set of eyes will help.

I'm trying to upload a file to the server using GWT (using UiBinder) on Google App Engine. Following all the examples I can find, things look like they should work, but the server's 'post' method is showing 0 items uploaded.

Here's the client code:

FormUploader.ui.xml:

<g:FormPanel action="/formImageUploader" ui:field="formPanel">
    <g:VerticalPanel>
      <g:FileUpload ui:field="uploader"></g:FileUpload>
      <g:Button ui:field="submit">Submit</g:Button>
    </g:VerticalPanel>
</g:FormPanel>

FormUploader.java:

 @UiField
  FormPanel formPanel;

  @UiField
  FileUpload uploader;

  @UiField
  Button submit;

 public FormUploader() {
    initWidget(uiBinder.createAndBindUi(this));

    formPanel.setEncoding(FormPanel.ENCODING_MULTIPART);
    formPanel.setMethod(FormPanel.METHOD_POST);
    addSubmitHandlers();
  }

  private void addSubmitHandlers() {    
    formPanel.addSubmitCompleteHandler(new SubmitCompleteHandler() {
      @Override
      public void onSubmitComplete(SubmitCompleteEvent event) {
        Core.log("Inside submitComplete");
        Window.alert(event.getResults());
      }
    });
  }

web.xml:

  <servlet>
    <servlet-name>formImageUploader</servlet-name>
    <servlet-class>com.company.server.FormImageUploader</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>formImageUploader</servlet-name>
    <url-pattern>/formImageUploader</url-pattern>
  </servlet-mapping>

FormImageUploader.java:

public class FormImageUploader extends HttpServlet {

  protected void doPost(HttpServletRequest request, HttpServletResponse resp) throws ServletException, IOException {
    log.info("Request.getContentLength(): " + request.getContentLength());
    log.info("Content type: " + request.getContentType());

    log.info("Parm names: ");
    for (Object paramName : request.getParameterMap().keySet()) {
      log.info("__Param name: " + paramName.toString());
    }
    log.info("Attribtue names: ");
    Enumeration enu = request.getAttributeNames();
    while (enu.hasMoreElements()) {
      log.info("__Attribute name: " + enu.nextElement().toString());
    }
    log.info("Header names: ");
    enu = request.getHeaderNames();
    while (enu.hasMoreElements()) {
      log.info("__Header name: " + enu.nextElement().toString());
    }
    resp.getWriter().println("Post Code has finished executing"); 
}

All this executes just fine, the client shows an alert box containing Post Code has finished executing, but I can't figure out how to get the content of the file I want to upload.

Here's the server side log:

Request.getContentLength(): 63
Content type: multipart/form-data; boundary=---------------------------194272772210834546661
Parm names:
Attribtue names:
__Attribute name: com.google.apphosting.runtime.jetty.APP_VERSION_REQUEST_ATTR

Can anyone help me see why I'm not seeing any files on the server after clicking "submit" on the client?

Thanks in advance

Edit:

Still can't figure out why I can't get the form data on the server. I'm following this example, and just trying to write the response out to the client, but the server is definitely not seeing any content from the client.

Server code:

try {
      log.info("Inside try block");
      ServletFileUpload upload = new ServletFileUpload();
      log.info("created the servlet file upload");
      res.setContentType("text/plain");
      log.info("set the content type to text/plain");
      FileItemIterator iterator = upload.getItemIterator(req);
      log.info("Got the iterator for items");
      while (iterator.hasNext()) {
        log.info("__inside iterator.hasNext");
        FileItemStream item = iterator.next();
        log.info("__assigned the file item stream");
        InputStream stream = item.openStream();
        log.info("__opened said stream");
        if (item.isFormField()) {
          log.warning("Got a form field: " + item.getFieldName());
        } else {
          log.warning("Got an uploaded file: " + item.getFieldName() +
              ", name = " + item.getName());

          int len;
          byte[] buffer = new byte[8192];
          while ((len = stream.read(buffer, 0, buffer.length)) != -1) {
            res.getOutputStream().write(buffer, 0, len);
          }
          log.info("Done writing the input to the output stream");
        }
      }
    } catch (Exception ex) {
      log.severe("Exception; " + ex);
      throw new ServletException(ex);
    }
    log.info("Done parseInput3");

Logs:

Inside parseInput3
Inside try block
created the servlet file upload
set the content type to text/plain
Got the iterator for items
Done parseInput3

Again, it's definitely not able to iterate over the file items on the server... any idea why?

Upvotes: 2

Views: 2261

Answers (2)

Cuga
Cuga

Reputation: 17904

Turns out, GAE has a completely different way of uploading files (Blobs): http://code.google.com/appengine/docs/java/blobstore/overview.html#Uploading_a_Blob

You need to supply the form's on the client with a Blobstore Upload URL (which times out) from the server, retrieved using:

blobstoreService.createUploadUrl('urlToRedirectToAfterUploadComplete')

After setting that upload URL on the form's action, the form will be submitted and the blobstore service will redirect to the supplied URL, which can then access the BlobKey of the object stored by Google.

So with this, I added the following ChangeHandler to my GWT FileUpload:

private void addChangeListener() {
    Core.log("Selected file: " + uploader.getFilename());
    uploader.addChangeHandler(new ChangeHandler() {
      @Override
      public void onChange(ChangeEvent event) {
        MyApp.SERVICES.getUploadUrl(new SuccessAsyncCallback<String>() {
          @Override
          public void onSuccess(String uploadUrl) {
            form.setAction(uploadUrl);
            form.submit();
            Core.log("Submitted form with upload url: " + uploadUrl);
          }
        });
      }
    });
  }

Then in the servlet I have the GAE redirect to after uploads:

 @Override
  protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws Exception {
    BlobstoreService blobstoreService = BlobstoreServiceFactory.getBlobstoreService();
    Map<String, List<BlobKey>> blobs = blobstoreService.getUploads(req);
    // where 'uploader' is the name of the FileUploader (<input type='file'>) control on the client
    List<BlobKey> blobKeys = blobs.get("uploader");
    resp.getOutputStream().print(blobKeys.get(0).getKeyString());
  }

Note!

GAE will throw a OutOfMemory exception when trying to get the uploaded blob's value if the FileUpload control doesn't have a name="whateveryouwantthenametobe" attribute defined, even if you tell it (using UiBinder) that the id is ui:field="nameofControl"

Upvotes: 2

Stuart Langley
Stuart Langley

Reputation: 7054

You cannot write to the local file system - you should take a look at the files api.

Upvotes: 0

Related Questions