Pregunta usando blobstore con google cloud endpoint y android


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 adaptarme? el ejemplo oficial utilizar puntos finales (en el caso de que deba usar las instancias de mi motor de aplicaciones) o usar getServingUrl (evitando mis instancias) para almacenar y servir mis blobs?
Por favor, en lugar de palabras, incluye el código. Gracias.


32
2018-04-26 23:42


origen


Respuestas:


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 estará  redirigido una vez que se ha completado la carga real. Ahí es donde  manejarás almacenar el blobkey y / o servir la url y devolverla al cliente. Deberá asignar un servlet a esa url, que manejará el paso 4

Paso 3 (cliente) Vuelva al cliente nuevamente 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ía al servlet en el paso 2, se redireccionará al servlet que especificó en el createUploadUrl() más temprano

Paso 4 (servidor)

De vuelta al lado del servidor: Este es el servlet que maneja la url mapeada para 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) En realidad, almacena 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 los usuarios de LDPI con solo agregar =sXXX al final de la url de servicio. 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.


32
2018-06-02 21:08



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://stackoverflow.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 unir /blob/upload para 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)


5
2017-07-21 13:15



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: Puedes hacerlo con un httpClient de Android, de nuevo, en mi caso necesito cargar desde un sitio web, luego utilizo un formulario y una devolución de llamada de evento onChangeFile () para obtener el uploadurl (usando el paso 3) y luego cuando responde para cambiar el formulario parámetros "acción" 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>

2
2018-02-27 19:50