android - gae - crear aplicacion con google app engine
usando blobstore con google cloud endpoint y android (3)
Estoy desarrollando un proyecto de Android conectado a la aplicación de motor usando el plugin eclipse. Un aspecto de la aplicación es permitir que el usuario Alpha envíe fotos al usuario Bravo. Para hacer eso tengo la siguiente configuración:
Publicación alfa del usuario:
- Enviar imagen al servidor del motor de mi aplicación a través de puntos finales
- servidor almacena imagen en blob store
- el servidor almacena blobkey en el almacén de datos
Usuario Bravo recibiendo:
- servidor obtiene blobkey del almacén de datos
- servidor obtiene imagen usando blob key
- el servidor envía la imagen a la aplicación de Android usando puntos finales
Esta configuración demora más de dos (2) minutos desde que mi aplicación Android envía una imagen a cuando puedo verla en la úlcera. No hace falta decir que esto es completamente inaceptable.
Mi servidor está procesando la imagen mediante programación, a través del siguiente código:
public static BlobKey toBlobstore(Blob imageData) throws FileNotFoundException, FinalizationException, LockException, IOException {
if (null == imageData)
return null;
// Get a file service
FileService fileService = FileServiceFactory.getFileService();
// Create a new Blob file with mime-type "image/png"
AppEngineFile file = fileService.createNewBlobFile("image/jpeg");// png
// Open a channel to write to it
boolean lock = true;
FileWriteChannel writeChannel = fileService.openWriteChannel(file, lock);
// This time we write to the channel directly
writeChannel.write(ByteBuffer.wrap
(imageData.getBytes()));
// Now finalize
writeChannel.closeFinally();
return fileService.getBlobKey(file);
}
¿Alguien sabe cómo puedo adaptar el ejemplo oficial para usar puntos finales (en el caso de que deba usar mis instancias de motor de aplicaciones) o usar getServingUrl
(omitiendo mis instancias) para almacenar y servir mis blobs?
Por favor, en lugar de palabras, incluye el código. Gracias.
Como traté de hacer el servicio de devolución de llamada en la API del punto final, aborto ese enfoque. Sin embargo, podría resolver ese problema haciendo un servlet paralelo al punto final de la API, solo necesita definir el servidor de la clase y agregarlo a la configuración web.xml. Aquí mi solución:
1 Enpoint Service para obtener la URL para cargar: Entonces el servicio podría estar protegido con clientId
@ApiMethod(name = "getUploadURL", httpMethod = HttpMethod.GET)
public Debug getUploadURL() {
String blobUploadUrl = blobstoreService.createUploadUrl("/update");
Debug debug = new Debug();
debug.setData(blobUploadUrl);
return debug;
}
2. Ahora el Cliente puede llamar al endpoint para obtener la URL de carga:
Tal vez algo como esto (para Android use su biblioteca de cliente también):
gapi.client.debugendpoint.getUploadURL().execute();
3. El siguiente paso es hacer una publicación en url atrapado en el último paso: puede hacerlo con un httpClient de android, de nuevo, en mi caso necesito cargar desde un sitio web, luego uso un formulario, y en el evento de devolución de eventos deChangeFile () obtener el uploadurl (usando el paso 3) cuando responda para cambiar los parámetros de forma "action" y "codeId" antes de que alguien decida hacer clic en el botón de enviar:
<form id="submitForm" action="put_here_uploadUrl" method="post" enctype="multipart/form-data">
<input type="file" name="image" onchange="onChangeFile()">
<input type="text" name="codeId" value=''put_here_some_dataId''>
<input type="submit" value="Submit"></form>
4 Finalmente, la clase de servlet paralela:
@SuppressWarnings("serial")
public class Update extends HttpServlet{
public void doPost(HttpServletRequest req, HttpServletResponse resp)
throws IOException {
String userId = req.getParameter("codeId");
List<BlobKey> blobs = BSF.getService().getUploads(req).get("image");
BlobKey blobKey = blobs.get(0);
ImagesService imagesService = ImagesServiceFactory.getImagesService();
ServingUrlOptions servingOptions = ServingUrlOptions.Builder.withBlobKey(blobKey);
String servingUrl = imagesService.getServingUrl(servingOptions);
resp.setStatus(HttpServletResponse.SC_OK);
resp.setContentType("application/json");
JSONObject json = new JSONObject();
try {
json.put("imageUrl", servingUrl);
json.put("codeId", "picture_of_"+userId);
json.put("blobKey", blobKey.getKeyString());
} catch (JSONException e){
e.printStackTrace();
}
PrintWriter out = resp.getWriter();
out.print(json.toString());
out.flush();
out.close();
}
}
y agregue a web.xml, donde com.apppack es el paquete de la clase de actualización
<servlet>
<servlet-name>update</servlet-name>
<servlet-class>com.apppack.Update</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>update</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
Compartiré cómo estoy haciendo esto. No estoy usando los puntos finales de google-cloud, sino solo mi propia API basada en el resto, pero debería ser la misma idea de cualquier manera.
Lo expondré paso a paso con el código, espero que quede claro. Simplemente adaptaría la forma en que envía sus solicitudes para usar puntos finales en lugar de hacerlo de manera más genérica como en este ejemplo. Estoy incluyendo algunos repetitivos, pero excluyendo try / catch, checking de errores, etc. para abreviar.
Paso 1 (cliente)
El primer cliente solicita una URL de carga del servidor:
HttpClient httpclient = new DefaultHttpClient();
HttpConnectionParams.setConnectionTimeout(httpclient.getParams(), 10000); //Timeout Limit
HttpGet httpGet = new HttpGet("http://example.com/blob/getuploadurl");
response = httpclient.execute(httpGet);
Paso 2 (servidor)
En el lado del servidor, el servlet de solicitud de carga se vería así:
String blobUploadUrl = blobstoreService.createUploadUrl("/blob/upload");
res.setStatus(HttpServletResponse.SC_OK);
res.setContentType("text/plain");
PrintWriter out = res.getWriter();
out.print(blobUploadUrl);
out.flush();
out.close();
tenga en cuenta el argumento de createUploadUrl. Aquí es donde el cliente será redirigido una vez que se haya completado la carga real. Ahí es donde manejarás el almacenamiento del blobkey y / o la publicación de la url y su devolución al cliente. Deberá asignar un servlet a esa url, que manejará el paso 4
Paso 3 (cliente) Regrese nuevamente al cliente para enviar el archivo real a la URL de carga usando la URL devuelta desde el paso 2.
HttpClient httpclient = new DefaultHttpClient();
HttpPost httppost = new HttpPost(uploadUrlReturnedFromStep2);
FileBody fileBody = new FileBody(thumbnailFile);
MultipartEntity reqEntity = new MultipartEntity();
reqEntity.addPart("file", fileBody);
httppost.setEntity(reqEntity);
HttpResponse response = httpclient.execute(httppost)
Una vez que esta solicitud se envíe al servlet en el paso 2, se redireccionará al servlet que especificó en createUploadUrl()
anteriormente.
Paso 4 (servidor)
De vuelta al lado del servidor: este es el servlet que maneja la url asignada a blob/upload
. Aquí volveremos la blobkey y la URL de servicio al cliente en un objeto json:
List<BlobKey> blobs = blobstoreService.getUploads(req).get("file");
BlobKey blobKey = blobs.get(0);
ImagesService imagesService = ImagesServiceFactory.getImagesService();
ServingUrlOptions servingOptions = ServingUrlOptions.Builder.withBlobKey(blobKey);
String servingUrl = imagesService.getServingUrl(servingOptions);
res.setStatus(HttpServletResponse.SC_OK);
res.setContentType("application/json");
JSONObject json = new JSONObject();
json.put("servingUrl", servingUrl);
json.put("blobKey", blobKey.getKeyString());
PrintWriter out = res.getWriter();
out.print(json.toString());
out.flush();
out.close();
Paso 5 (cliente)
Obtendremos la blobkey y la URL de servicio del json y luego la enviaremos junto con el ID de usuario, etc. para almacenar en la entidad del almacén de datos.
JSONObject resultJson = new JSONObject(resultJsonString);
String blobKey = resultJson.getString("blobKey");
String servingUrl = resultJson.getString("servingUrl");
List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>(2);
nameValuePairs.add(new BasicNameValuePair("userId", userId));
nameValuePairs.add(new BasicNameValuePair("blobKey",blobKey));
nameValuePairs.add(new BasicNameValuePair("servingUrl",servingUrl));
HttpClient httpclient = new DefaultHttpClient();
HttpConnectionParams.setConnectionTimeout(httpclient.getParams(), 10000);
HttpPost httppost = new HttpPost(url);
httppost.setEntity(new UrlEncodedFormEntity(nameValuePairs));
HttpResponse response = httpclient.execute(httppost);
// Continue to store the (immediately available) serving url in local storage f.ex
Paso 6 (servidor) Almacenar realmente todo en el almacén de datos (usando Objectify en este ejemplo)
final String userId = req.getParameter("userId");
final String blobKey = req.getParameter("blobKey");
final String servingUrl = req.getParameter("servingUrl");
ExampleEntity entity = new ExampleEntity();
entity.setUserId(userId);
entity.setBlobKey(blobKey);
entity.setServingUrl(servingUrl);
ofy().save().entity(entity);
Espero que esto aclare las cosas. Si alguien quiere editar la respuesta para usar los puntos finales de la nube en lugar de este ejemplo más genérico, siéntase libre :)
Acerca de la URL de servicio
La URL de publicación es una excelente manera de servir imágenes a sus clientes, debido a la forma en que puede escalar dinámicamente las imágenes sobre la marcha. Por ejemplo, puede enviar imágenes más pequeñas a sus usuarios de LDPI simplemente añadiendo =sXXX
al final de la url de publicación. Donde XXX es el tamaño de píxel de la dimensión más grande de su imagen. Evita por completo sus instancias y solo paga por el ancho de banda, y el usuario solo descarga lo que necesita.
¡PD!
Debería ser posible detenerse en el paso 4 y simplemente almacenarlo directamente allí, pasando userId f.ex en el paso 3. Se supone que todos los parámetros deben enviarse al Paso 4, pero no conseguí que funcionen, por lo que este es cómo lo hago en este momento, así que lo estoy compartiendo de esta manera, ya que sé que funciona.
Utilicé la respuesta de esta pregunta para construir mi propio sistema que usa Endpoints de AppEngine. A diferencia de las publicaciones anteriores, quiero tener una API limpia que transmita directamente la imagen (como matriz de bytes) a Google Endpoint y la carga a BlobstorageService se realiza en el lado del back-end. El beneficio de eso es que tengo una API atómica. El inconveniente es obviamente la carga en el servidor, así como las operaciones de clasificación pesadas en el cliente.
Android: cargar, escalar y serializar imágenes y subirlas a puntos finales
void uploadImageBackground(Bitmap bitmap) throws IOException {
// Important! you wanna rescale your bitmap (e.g. with Bitmap.createScaledBitmap)
// as with full-size pictures the base64 representation would not fit in memory
// encode bitmap into byte array (very resource-wasteful!)
ByteArrayOutputStream stream = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream);
byte[] byteArray = stream.toByteArray();
bitmap.recycle();
bitmap = null;
stream = null;
// Note: We encode ourselves, instead of using image.encodeImageData, as this would throw
// an ''Illegal character ''_'' in base64 content'' exception
// See: http://.com/questions/22029170/upload-photos-from-android-app-to-google-cloud-storage-app-engine-illegal-char
String base64 = Base64.encodeToString(byteArray, Base64.DEFAULT);
byteArray = null;
// Upload via AppEngine Endpoint (ImageUploadRequest is a generated model)
ImageUploadRequest image = new ImageUploadRequest();
image.setImageData(base64);
image.setFileName("picture.png");
image.setMimeType("image/png");
App.getMyApi().setImage(image).execute();
}
Extremo de API de Backend: carga la imagen en BlobstorageService
@ApiMethod(
name = "setImage",
path = "setImage",
httpMethod = ApiMethod.HttpMethod.POST
)
public void saveFoodImageForUser(ImageUploadRequest imageRequest) throws IOException {
assertNotEmpty(userId, "userId");
assertNotNull(imageRequest, "imageRequest");
// create blob url
BlobstorageService blobService = BlobstoreServiceFactory.getBlobstoreService();
String uploadUrl = blobService.createUploadUrl("/blob/upload");
// create multipart body containing file
HttpEntity requestEntity = MultipartEntityBuilder.create()
.addBinaryBody("file", imageRequest.getImageData(),
ContentType.create(imageRequest.getMimeType()), imageRequest.getFileName())
.build();
// Post request to BlobstorageService
// Note: We cannot use Apache HttpClient, since AppEngine only supports Url-Fetch
// See: https://cloud.google.com/appengine/docs/java/sockets/
URL url = new URL(uploadUrl);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setDoOutput(true);
connection.setRequestMethod("POST");
connection.addRequestProperty("Content-length", requestEntity.getContentLength() + "");
connection.addRequestProperty(requestEntity.getContentType().getName(), requestEntity.getContentType().getValue());
requestEntity.writeTo(connection.getOutputStream());
// BlobstorageService will forward to /blob/upload, which returns our json
String responseBody = IOUtils.toString(connection.getInputStream());
if(connection.getResponseCode() < 200 || connection.getResponseCode() >= 400) {
throw new IOException("HTTP Status " + connection.getResponseCode() + ": " + connection.getHeaderFields() + "/n" + responseBody);
}
// parse BlopUploadServlet''s Json response
ImageUploadResponse response = new Gson().fromJson(responseBody, ImageUploadResponse.class);
// save blobkey and serving url ...
}
Servlet que maneja la devolución de llamada desde BlobstorageService
public class BlobUploadServlet extends HttpServlet {
@Override
public void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
BlobstorageService blobService = BlobstoreServiceFactory.getBlobstoreService();
List<BlobKey> blobs = blobService.getUploads(req).get("file");
if(blobs == null || blobs.isEmpty()) throw new IllegalArgumentException("No blobs given");
BlobKey blobKey = blobs.get(0);
ImagesService imagesService = ImagesServiceFactory.getImagesService();
ServingUrlOptions servingOptions = ServingUrlOptions.Builder.withBlobKey(blobKey);
String servingUrl = imagesService.getServingUrl(servingOptions);
res.setStatus(HttpServletResponse.SC_OK);
res.setContentType("application/json");
// send simple json response (ImageUploadResponse is a POJO)
ImageUploadResponse result = new ImageUploadResponse();
result.setBlobKey(blobKey.getKeyString());
result.setServingUrl(servingUrl);
PrintWriter out = res.getWriter();
out.print(new Gson().toJson(result));
out.flush();
out.close();
}
}
Lo único que queda por hacer es vincular /blob/upload
a UploadBlobServlet.
Nota : Esto no parece funcionar cuando AppEngine se ejecuta localmente (si se ejecuta localmente, entonces el POST a BlobstorageService siempre devolvería un 404 NOT FOUND)