@brunofuster

www.wesaveapp.com

uploading an image from iphone to appengine blobstore using vraptor

with 8 comments

Its quite simple to do this. I will represent the back-end using vraptor, a really great mvc web framework.

Well, at first, you need to generate the upload URI, provided by GAE’s SDK. When you’re working with web forms, you can just call the BlobstoreService to create the Upload URI directly from your JSP, but this is not the case.

We will provide then a resource that will return an URI for the upload.


@Resource
@Path("/image")
public class ImageController {

    private final BlobstoreSevice blobstoreSevice = BlobstoreServiceFactory.getBlobstoreService();
    private final Result result;

    public ImageController(Result result) {
        this.result = result;
    }

    @Path("/createUploadURI")
    public void uploadURI() {
        
        String uploadURI = blobstoreService.createUploadUrl("/image/upload");
        result.use(http()).body(uploadURI);
    }

}

That’s enough to return the upload URI into the response body (we wont use json or xml at this time to simplify the post).
Just for the record, there’s an issue when running this at development server. Blobstore won’t return the URI with its host, although it will return the absolute URI when at production environment.

Lets consume this resource from iphone now using asihttp.

#import "ASIHTTPRequest.h"
//...

-(void) getUploadURI {

	NSURL *uri = [NSURL URLWithString:@"http://yourserver.appspot.com/image/createUploadURI"]];
	ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:uri];
	[request setDelegate:self];
	[request startAsynchronous];
}

-(void) requestFinished:(ASIHTTPRequest *)request {

	NSString *uri = [request responseData];
}

-(void) requestFailed:(ASIHTTPRequest *)request {
	
	NSError *error = [request error];
	NSLog(@"error %@", error);
}

Now we have the URI to upload an image. Lets create a new objective-c class to handle the upload using asihttp form data (notice that we will also track the upload progress).

#import <Foundation/Foundation.h>
#import "ASIFormDataRequest.h"

@interface ImageUpload : NSObject {

	ASIFormDataRequest *formData;
	float progress;
}

@property (nonatomic, retain) ASIFormDataRequest *formData;
@property (nonatomic, assign) float progress;

-(id) initWithImage:(UIImage*)img uri:(NSString*)uri;
-(void) startUpload;

@end

And it’s implementation:

#import "ImageUpload.h"

@implementation ImageUpload

@synthesize formData, progress;

-(id) initWithImage:(UIImage*)img uri:(NSString*)uri {
	
	NSData *imageData = [NSData dataWithData:UIImagePNGRepresentation(img)];

	self.formData = [[ASIFormDataRequest requestWithURL:[NSURL URLWithString:uri]] retain];	
	[self.formData setData:imageData withFileName:@"defaultImage.png" andContentType:@"image/png" forKey:@"defaultImage"];
	[self.formData setDelegate:self];
	[self.formData setUploadProgressDelegate:self];
	
	return self;
}

-(void) startUpload {

	[self.formData startAsynchronous];
}

#pragma mark Upload Progress Tracking

- (void)request:(ASIHTTPRequest *)theRequest didSendBytes:(long long)newLength {
	
	if ([theRequest totalBytesSent] > 0) {
		float progressAmount = (float) ([theRequest totalBytesSent]/[theRequest postLength]);
		self.progress = progressAmount;
	}
	
}

-(void) requestFinished:(ASIHTTPRequest *)request {
	NSLog(@"upload finished");
}

-(void) requestFailed:(ASIHTTPRequest *)request {
	NSError *error = [request error];
	NSLog(@"error %@", error);
}

- (void) dealloc
{
	[self.formData release];
	[super dealloc];
}
@end

Let’s change the requestFinished: method for /image/getUploadURI to start the upload after its response is done.

#import "ImageUpload.h"
//...
-(void) requestFinished:(ASIHTTPRequest *)request {

	ImageUpload *imageUpload = [[ImageUpload alloc] initWithImage:self.imageView.image uri:[request responseData]];
	[imageUpload startUpload];
}

If you want to track the progress at this view, you can create your own delegate like [imageUpload setUploadTrackingProgress:self] or expose the asihttp method setUploadProgressDelegate:.

At last we will create the resource that Blobstore will call on your server after the upload has been completed. You can now use the Images Service to get an URI for this image and store it.

@Path("/upload")
public void upload() {
	Map<String, BlobKey> blobs = blobstoreService.getUploadedBlobs(request);
	BlobKey blob = blobs.get("defaultImage");
		
	ImagesService imagesService = ImagesServiceFactory.getImagesService();
	String imageUrl = imagesService.getServingUrl(blob);

	/* datastore.put(imageurl) */
	result.use(Results.http()).body(imageUrl);
}

We just need to inject the current HttpServletRequest for the BlobstoreService like this:

@Resource
@Path("/image")
public class ImageController {

    private final BlobstoreSevice blobstoreSevice = BlobstoreServiceFactory.getBlobstoreService();
    private final HttpServletRequest request;
    private final Result result;

    public ImageController(Result result, HttpServletRequest request) {
        this.result = result;
        this.request = request;
    }
...

References:
vraptor
asihttp
Blobstore Service
Images Service
http://ikaisays.com/2010/09/08/gwt-blobstore-the-new…

Advertisements

Written by brunofuster

March 11, 2011 at 3:06 pm

8 Responses

Subscribe to comments with RSS.

  1. Great post! This has been so far the single relevant resource available online.

    I didn’t use vraptor so have a problem to understand the code.
    Could you please clarify what @Resource and @Path do? Or how can I convert this code without using vraptor?

    ToyHunter

    March 1, 2012 at 11:46 am

    • Hi! Sorry for taking so long to answer.

      @Resource is just a annotation so VRaptor can scan controllers and inject Resources.
      @Path is just to tell what URL will that method attend to.

      So just use the same methods and configure the right URL for them instead of using @Resource and @Path.

      brunofuster

      March 18, 2012 at 1:03 pm

  2. The appengine server responds with a content-length error when I’m trying it. Any suggestions?

    Gal Skarishevsky

    March 1, 2012 at 8:06 pm

    • Hi!

      Are you sending the content-lenght header properly?
      Debug your back-end method and check that http request header.

      brunofuster

      March 18, 2012 at 1:05 pm

  3. Appengine server responds with “content-length” error. Any idea?

    Gal Skarishevsky

    March 1, 2012 at 8:07 pm

  4. So in order to post an image, you would need to send 2 HTTP request from the client? 1 for retrieving the upload URL and another to post the image to the upload URL? and if we want to save the blobkey you would then have to send another request. Is there a best practice tips here?

    Mike

    June 18, 2013 at 8:27 pm

    • You already have the BlobKey -> BlobKey blob = blobs.get(“defaultImage”);
      Yes, you need to make 1 request for the upload URL and another for the image upload… its how appengine works

      brunofuster

      June 18, 2013 at 8:31 pm

  5. So on the Google app engine backend (I’m using endpoints) where do you create the ImageController?

    sierra

    September 12, 2015 at 7:50 pm


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

w

Connecting to %s

%d bloggers like this: