32 votes

En utilisant le blobstore avec google cloud endpoint et android

Je développe une application Android connectée à app-engine en utilisant le plugin eclipse. Un aspect de l'application est de permettre à l'utilisateur Alpha d'envoyer des images à l'utilisateur Bravo. Pour ce faire, j'ai la configuration suivante :

Publication de l'utilisateur Alpha :

  • envoyer l'image à mon serveur app engine via endpoints
  • le serveur stocke l'image dans le blob store
  • le serveur stocke le blobkey dans le datastore

Récupération de l'utilisateur Bravo :

  • le serveur récupère le blobkey depuis le datastore
  • le serveur récupère l'image en utilisant la clé blob
  • le serveur envoie l'image à l'application Android en utilisant endpoints

Cette configuration prend plus de deux (2) minutes à partir du moment où mon application Android envoie une image jusqu'à ce que je puisse la voir dans le blob store. Inutile de dire que c'est complètement inacceptable.

Mon serveur traite l'image de manière programmée, avec le code suivant :

public static BlobKey toBlobstore(Blob imageData) throws FileNotFoundException, FinalizationException, LockException, IOException {
        if (null == imageData)
            return null;

        // Obtenez un service de fichiers
        FileService fileService = FileServiceFactory.getFileService();

        // Créez un nouveau fichier Blob avec le type MIME "image/png"
        AppEngineFile file = fileService.createNewBlobFile("image/jpeg");// png

        // Ouvrez un canal d'écriture
        boolean lock = true;
        FileWriteChannel writeChannel = fileService.openWriteChannel(file, lock);

        // Cette fois, nous écrivons directement sur le canal
        writeChannel.write(ByteBuffer.wrap
            (imageData.getBytes()));

        // Finaliser maintenant
        writeChannel.closeFinally();
        return fileService.getBlobKey(file);
    }

Est-ce que quelqu'un sait comment je peux soit adapter l'exemple officiel pour utiliser endpoints (dans le cas où je dois utiliser mes instances app-engine) ou utiliser getServingUrl (contournant mes instances) pour stocker et servir mes blobs ?
S'il vous plaît, au lieu de mots, incluez le code. Merci.

32voto

Joachim Points 317

Je vais partager comment je fais cela. Je n'utilise pas google-cloud-endpoints, mais juste mon propre API basé sur REST, mais cela devrait être la même idée de toute façon.

Je vais le décrire étape par étape avec du code, avec un peu de chance ce sera clair. Vous adapteriez simplement la manière dont vous envoyez vos requêtes pour utiliser des endpoints au lieu de le faire de manière plus générique comme dans cet exemple. J'inclus un peu de code de base, mais j'exclus les blocs try/catch, la vérification des erreurs, etc. pour la concision.

Étape 1 (client)

Tout d'abord, le client demande une URL de téléversement au serveur :

HttpClient httpclient = new DefaultHttpClient();    
HttpConnectionParams.setConnectionTimeout(httpclient.getParams(), 10000); //Limite de temps d'attente

HttpGet httpGet = new HttpGet("http://example.com/blob/getuploadurl");
response = httpclient.execute(httpGet);

Étape 2 (serveur)

Côté serveur, la servlet de demande de téléversement ressemblerait à ceci :

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();

notez l'argument à createUploadUrl. C'est là où le client sera redirigé une fois que le téléversement réel aura été effectué. C'est là que vous gérerez le stockage de la blobkey et/ou l'URL de serveur et le renverrez au client. Vous devrez mapper une servlet pour cette URL, qui gèrera l'étape 4

Étape 3 (client) Retour au client pour envoyer le fichier réel à l'URL de téléversement en utilisant l'URL renvoyée à partir de l'étape 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)

Une fois que cette requête est envoyée à la servlet de l'étape 2, elle sera redirigée vers la servlet que vous avez spécifiée dans le createUploadUrl() précédemment

Étape 4 (serveur)

Retour côté serveur : Voici la servlet qui gère l'URL mappée à blob/upload. Nous allons ici renvoyer la blobkey et l'URL de serveur au client dans un objet json :

List 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();

Étape 5 (client)

Nous obtiendrons la blobkey et l'URL de serveur du json, puis nous l'enverrons avec l'ID utilisateur, etc. pour le stocker dans l'entité du datastore.

JSONObject resultJson = new JSONObject(resultJsonString);

String blobKey = resultJson.getString("blobKey");
String servingUrl = resultJson.getString("servingUrl");

List nameValuePairs = new ArrayList(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);

// Continuer à stocker l'URL de serveur (immédiatement disponible) dans le stockage local par exemple

Étape 6 (serveur) Stocker réellement tout dans le datastore (en utilisant objectify dans cet exemple)

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);

J'espère que cela rend les choses plus claires. Si quelqu'un veut éditer la réponse pour utiliser des points de terminaison cloud au lieu de cet exemple plus générique, n'hésitez pas :)

A propos de l'URL de serveur

L'URL de serveur est un excellent moyen de servir des images à vos clients, en raison de la façon dont elle peut redimensionner dynamiquement les images à la volée. Par exemple, vous pouvez envoyer des images plus petites à vos utilisateurs LDPI en ajoutant simplement =sXXX à la fin de l'URL de serveur. Où XXX est la taille en pixels de la plus grande dimension de votre image. Vous évitez complètement vos instances et ne payez que pour la bande passante, et l'utilisateur ne télécharge que ce dont il a besoin.

PS!

Il devrait être possible de s'arrêter à l'étape 4 et de le stocker directement là, en passant par exemple l'ID utilisateur à l'étape 3. Tous les paramètres doivent être envoyés à l'étape 4, mais je n'ai pas réussi à le faire fonctionner, c'est donc ainsi que je le fais actuellement, donc je le partage de cette façon car je sais que cela fonctionne.

5voto

Flo Points 769

J'ai utilisé la réponse à cette question pour construire mon propre système qui utilise AppEngine Endpoints. Contrairement aux messages ci-dessus, je veux avoir une API propre qui transmet directement l'image (sous forme de tableau d'octets) à Google Endpoint et le téléchargement vers BlobstorageService est effectué du côté du serveur. L'avantage est d'avoir une API atomique. L'inconvénient est évidemment la charge sur le serveur ainsi que les lourdes opérations de marshalling du côté du client.

Android - charger, mettre à l'échelle et sérialiser l'image et la télécharger vers les endpoints

void uploadImageBackground(Bitmap bitmap) throws IOException {
    // Important! vous voulez redimensionner votre bitmap (par exemple avec Bitmap.createScaledBitmap)
    // car avec des images en pleine résolution, la représentation en base64 ne tiendrait pas en mémoire

    // encoder le bitmap en tableau d'octets (très gourmand en ressources!)
    ByteArrayOutputStream stream = new ByteArrayOutputStream();
    bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream);
    byte[] byteArray = stream.toByteArray();
    bitmap.recycle();
    bitmap = null;
    stream = null;

    // Remarque : Nous encodons nous-mêmes, au lieu d'utiliser image.encodeImageData, car cela lèverait
    //         une exception 'Caractère illégal '_' dans le contenu base64'
    // Voir : 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;

    // Téléchargement via AppEngine Endpoint (ImageUploadRequest est un modèle généré)
    ImageUploadRequest image = new ImageUploadRequest();
    image.setImageData(base64);
    image.setFileName("image.png");
    image.setMimeType("image/png");
    App.getMyApi().setImage(image).execute();
}

API Backend Endpoint - Télécharger l'image vers BlobstorageService

@ApiMethod(
        name = "setImage",
        path = "setImage",
        httpMethod = ApiMethod.HttpMethod.POST
)
public void saveFoodImageForUser(ImageUploadRequest imageRequest) throws IOException {
    assertNotEmpty(userId, "userId");
    assertNotNull(imageRequest, "imageRequest");

    // créer l'URL du blob
    BlobstorageService blobService = BlobstoreServiceFactory.getBlobstoreService();
    String uploadUrl = blobService.createUploadUrl("/blob/upload");

    // créer un corps multipart contenant le fichier
    HttpEntity requestEntity = MultipartEntityBuilder.create()
            .addBinaryBody("file", imageRequest.getImageData(),
                    ContentType.create(imageRequest.getMimeType()), imageRequest.getFileName())
            .build();

    // Envoyer une requête POST à BlobstorageService
    // Remarque : Nous ne pouvons pas utiliser Apache HttpClient, car AppEngine ne prend en charge que Url-Fetch
    //  Voir : 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 redirigera vers /blob/upload, qui renverra notre 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);
    }

    // analyser la réponse JSON de BlopUploadServlet
    ImageUploadResponse response = new Gson().fromJson(responseBody, ImageUploadResponse.class);

    // enregistrer la clé de blob et l'URL de service ...
}

Servelet qui gère le retour du BlobstorageService

public class BlobUploadServlet extends HttpServlet {
    @Override
    public void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
        BlobstorageService blobService = BlobstoreServiceFactory.getBlobstoreService();
        List blobs = blobService.getUploads(req).get("file");
        if(blobs == null || blobs.isEmpty()) throw new IllegalArgumentException("Aucun blob donné");

        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");

        // envoyer une réponse JSON simple (ImageUploadResponse est un 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();
    }
}

La seule chose restante à faire est de lier /blob/upload à UploadBlobServlet.

Remarque : Cela ne semble pas fonctionner lorsque AppEngine est exécuté localement (si exécuté localement, alors la POST à BlobstorageService retournerait toujours un 404 NOT FOUND)

2voto

Campino Points 26

Depuis que j'ai essayé de faire le service de rappel de différentes manières dans l'API de l'endpoint, j'ai abandonné cette approche. Cependant, j'ai pu résoudre ce problème en créant un servlet parallèle à l'API de l'endpoint, il suffit de définir la classe serveur et de l'ajouter à la configuration web.xml. Voici ma solution :

1 Service d'Endpoint pour obtenir l'URL de téléchargement : Ensuite, le service peut être protégé avec un 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. Maintenant, le Client peut appeler l'endpoint pour obtenir l'URL de téléchargement :
Peut-être quelque chose comme ceci (pour une utilisation sur Android, utilisez également votre bibliothèque cliente endpoint) :

gapi.client.debugendpoint.getUploadURL().execute(); 

3. L'étape suivante consiste à faire un POST vers l'URL capturée à la dernière étape : Vous pouvez le faire avec un httpClient Android, encore une fois, dans mon cas j'ai besoin de télécharger depuis un site web, donc j'utilise un formulaire et l'événement de rappel onChangeFile() pour obtenir l'URL de téléchargement (en utilisant l'étape 3) puis lorsque la réponse change, modifier les paramètres du formulaire "action" et "codeId" avant que quelqu'un décide de cliquer sur le bouton Soumettre :

4. Enfin la classe servlet parallèle :

@SuppressWarnings("serial")
public class Update  extends HttpServlet{

    public void doPost(HttpServletRequest req, HttpServletResponse resp)
            throws IOException {    

        String userId   = req.getParameter("codeId");

        List 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", "image_de_"+userId);
            json.put("blobKey",  blobKey.getKeyString());
        } catch (JSONException e){

            e.printStackTrace();            
        }

        PrintWriter out = resp.getWriter();
        out.print(json.toString());
        out.flush();
        out.close();
    }
}

et ajoutez à web.xml, où com.apppack est le package de la classe Update

update
com.apppack.Update

update
/*

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X