Christian Lanz Christian Lanz - 1 month ago 29
Android Question

Google App Engine Java HTTP Post Image from API Method to Servlet

I hope someone can help me out.

I want to send a url as a string to the client endpoint function and then I want the endpoint function to download the image and send it then via a HTTP Post request to my servlet (also running on GAE).

The problem is - there is no image posted at all.

It's strange because if I use the exact same code (the HttpPost class) on an android client, it works fine - the image gets posted to the servlet and the servlet stores the image into the datastore / blobstore.

Isn't it possible to send a HTTP Post request from a client endpoint function to a servlet?


Solved, see answer below!





Android:

BackendApi.anyMethod("url-to-any-image").execute();





Client Endpoint Function:

@ApiMethod(path = "anyMethod")
public void anyMethod(@Named("url") String url) {
// --------------------------------------------------
// No input validation here - just a proof of concept
// --------------------------------------------------

try {
// Download image
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
Resources.asByteSource(new URL(url)).copyTo(buffer);

// Upload image
HttpPost httpPost = new HttpPost();
httpPost.setTarget(new URL(BlobstoreServiceFactory.getBlobstoreService().createUploadUrl("/upload")));
httpPost.add("image", buffer.toByteArray());
httpPost.send();
} catch (IOException e) {
LOG.log(Level.WARNING, e.getMessage(), e);
}
}





HttpPost Class:

public class HttpPost {

private final static String CRLF = "\r\n";

private String boundary;

private URL url;
private ByteArrayOutputStream buffer;

public HttpPost() {
// Generate random boundary
// Boundary length: max. 70 characters (not counting the two leading hyphens)
byte[] random = new byte[40];
new Random().nextBytes(random);
boundary = Base64.encodeBase64String(random);

// Init buffer
buffer = new ByteArrayOutputStream();
}

public void setTarget(URL url) {
this.url = url;
}

public void add(String key, String value) throws IOException {
addToBuffer("--" + boundary + CRLF);
addToBuffer("Content-Disposition: form-data; name=\"" + key + "\"" + CRLF);
addToBuffer("Content-Type: text/plain; charset=UTF-8" + CRLF + CRLF);
addToBuffer(value + CRLF);
}

public void add(String key, byte[] fileBytes) throws IOException {
addToBuffer("--" + boundary + CRLF);
addToBuffer("Content-Disposition: form-data; name=\"" + key + "\"; filename=\"" + key + "\"" + CRLF);
addToBuffer("Content-Type: application/octet-stream" + CRLF);
addToBuffer("Content-Transfer-Encoding: binary" + CRLF + CRLF);
addToBuffer(fileBytes);
addToBuffer(CRLF);
}

public void send() throws IOException {
// Add boundary end
addToBuffer("--" + boundary + "--" + CRLF);

// Open url connection
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setDoOutput(true);
connection.setRequestMethod("POST");
connection.setRequestProperty("Connection", "Keep-Alive");
connection.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);
connection.setRequestProperty("User-Agent", "Google App Engine");

// Open data output stream
DataOutputStream request = new DataOutputStream(connection.getOutputStream());
request.write(buffer.toByteArray());
request.flush();
request.close();

// Close connection
connection.disconnect();
}

private void addToBuffer(String string) throws IOException {
buffer.write(string.getBytes());
}

private void addToBuffer(byte[] bytes) throws IOException {
buffer.write(bytes);
}
}





Http Servlet:

public class Upload extends HttpServlet {
private static final Logger LOG = Logger.getLogger(Upload.class.getName());
private BlobstoreService blobstoreService = BlobstoreServiceFactory.getBlobstoreService();

public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException {
Map<String, List<BlobKey>> blobs = blobstoreService.getUploads(request);

List<BlobKey> blobKeys = blobs.get("image");

if (blobKeys == null) {
LOG.warning("No blobkeys found");
return;
}

// Get blob key
BlobKey blobKey = blobKeys.get(0);

if (blobKey == null) {
LOG.warning("No blobkey found");
return;
}

// Create new image object
Image image = new Image();
image.setBlobKey(blobKey);
image.setTimestamp(new Date());

// Save image to datastore
OfyService.ofy().save().entity(image).now();

LOG.log(Level.INFO, "Image upload successful");
}
}

Answer

Accordingly to the Google App Engine Docs you are not allowed to fetch your own URL:

To prevent an app from causing an endless recursion of requests, a request handler is not allowed to fetch its own URL. It is still possible to cause an endless recursion with other means, so exercise caution if your app can be made to fetch requests for URLs supplied by the user.

That means the only way of doing this is to download the image on the Android client and then post it to the HttpServlet.

Android:

try {
    // Download image to the Android client
    ByteArrayOutputStream buffer = new ByteArrayOutputStream();
    Resources.asByteSource(new URL(url)).copyTo(buffer);

    // Upload image to HttpServlet
    HttpPost httpPost = new HttpPost();
    httpPost.setTarget(new URL("http-servlet-upload-url"));
    httpPost.add("image", buffer.toByteArray());
    httpPost.send();
  } catch (IOException e) {
    Logcat.error(e.getMessage());
  }
Comments