utiliser blobstore avec Google cloud endpoint et android

je développe une app-engine connected android project en utilisant le plugin eclipse. Un aspect de l'application est de permettre à L'utilisateur Alpha d'envoyer des photos à L'utilisateur Bravo. Pour ce faire, j'ai la configuration suivante:

Utilisateur Alpha affichage:

  • envoyer l'image à mon serveur app engine à travers les endpoints
  • server stocke les images dans le magasin d'objets blob
  • server stocke blobkey dans le magasin de données

Utilisateur Bravo:

  • serveur reçoit blobkey de la banque de données
  • serveur reçoit image blob à l'aide de la touche
  • serveur envoie l'image à l'application android à l'aide de points de terminaison

cette configuration prend vers le haut de deux (2) minutes à partir de quand mon application android envoie une image à quand je peux le voir dans la plaie blob. Inutile de dire que cela est totalement inacceptable.

My le serveur traite l'image par programmation, À travers le code suivant:

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

est-ce que quelqu'un sait comment je peux adapter l'exemple officiel pour utiliser des endpoints (dans le cas où je dois utiliser mes instances app-engine) ou utiliser getServingUrl (contourner mes instances) pour stocker et servir mes blobs?

Veuillez, au lieu de mots, inclure le code. Grâce.

32
demandé sur Lazy Ninja 2013-04-27 03:42:17

3 réponses

je vais partager comment je fais ça. Je n'utilise pas les endpoints de google-cloud, mais juste ma propre api rest, mais ça devrait être la même idée de toute façon.

je vais le présenter étape par étape avec le code, j'espère qu'il sera clair. Vous n'avez qu'à adapter la façon dont vous envoyez vos requêtes pour utiliser les endpoints au lieu de le faire de manière plus générique comme dans cet exemple. Je suis en incluant certains boilerplate,mais en excluant try/catch, erreur de vérification etc pour la brièveté.

Étape 1 (client)

le premier client demande une url de téléchargement à partir du serveur:

HttpClient httpclient = new DefaultHttpClient();    
HttpConnectionParams.setConnectionTimeout(httpclient.getParams(), 10000); //Timeout Limit

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

Étape 2 (serveur)

du côté du serveur, le servlet de demande de téléchargement ressemblerait à quelque chose comme 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 pour createUploadUrl. C'est là que le client sera redirigé une fois le téléchargement terminé. C'est là vous vous chargerez de stocker le blobkey et / ou de servir l'url et de le retourner au client. Vous aurez à mapper un servlet à cette url, qui traitera l'étape 4

Étape 3 (client) De nouveau au client pour envoyer le fichier réel à l'url de téléchargement en utilisant l'url retournée 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 cette requête envoyée au servlet à l'étape 2, elle sera redirigée vers le servlet you spécifié dans le createUploadUrl() antérieur à

Étape 4 (serveur)

retour au côté serveur: C'est le servlet qui gère l'url mappée à blob/upload . Nous retournerons ici le blobkey et l'url de service au client dans un objet 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();

Étape 5 (client)

nous obtiendrons le blobkey et l'url de service de la json et puis l'envoyer le long avec l'identifiant de l'utilisateur etc à stocker dans l'entité de datastore.

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

l'Étape 6 (serveur) En fait, tout stocker 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 paramètres cloud au lieu de cet exemple plus générique, n'hésitez pas:)

à propos de l'url de service

l'url de service est un excellent moyen de servir des images à vos clients, en raison de la façon dont il peut dynamiquement échelle des 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 service. Où XXX est la taille du pixel 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 elle a besoin.

PS!

il devrait être possible de s'arrêter à l'étape 4 et de le stocker directement là, en passant par userId F. ex à l'étape 3. Tous les paramètres sont censés être envoyés à L'Étape 4, mais je ne l'ai pas fait fonctionner, donc c'est comme ça que je le fais en ce moment, donc je le partage de cette façon puisque je sais que ça fonctionne.

32
répondu Joachim 2013-06-02 21:08:05

j'ai utilisé la réponse à cette question pour construire mon propre système qui utilise des paramètres AppEngine. Contrairement aux messages ci-dessus, je veux avoir une API propre qui transmet directement l'image (comme byte array) à Google Endpoint et le téléchargement à BlobstorageService est fait sur le côté du backend. L'avantage est que j'ai une API atomique. L'inconvénient évidemment la charge sur le serveur ainsi que les lourdes opérations de marshalling sur le client.

Android - charger, dimensionner et sérialiser l'image et télécharger vers les endpoints

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: /q/upload-photos-from-android-app-to-google-cloud-storage-app-engine-illegal-character-1833/"picture.png");
    image.setMimeType("image/png");
    App.getMyApi().setImage(image).execute();
}

Backend API Endpoint-Upload image to 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 les poignées de rappel de 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();
    }
}

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

Note : Ce ne semble pas fonctionner quand AppEngine tourne localement (si exécuté localement, alors le POST à BlobstorageService retournerait toujours un 404 non trouvé)

5
répondu Flo 2015-07-21 13:15:34

depuis que j'ai essayé avec beaucoup de moyens de faire le service de rappel dans l'api de endpoint, j'annule cette approche. Cependant, je pourrais résoudre ce problème en faisant un servlet parallèle à l'extrémité de l'api, il suffit de définir le serveur de classe et de l'ajouter web.de configuration xml. Voici ma solution:

1 Service Enpoint pour obtenir l'URL de téléchargement: Alors le service peut être protégé avec 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 à endpoint pour obtenir L'URL de téléchargement:

Peut-être certains comme ceci (pour android Utilisez votre bibliothèque client enpoint aussi):

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

3. La prochaine étape est de faire un post À url catched dans la dernière étape: Vous pouvez le faire avec un httpClient d'android, encore une fois, dans mon cas j'ai besoin d'Uploader à partir d'un web, puis j'utilise un formulaire, et onChangeFile () event callback pour obtenir l'uploadurl (en utilisant l'étape 3) puis quand il réponse pour changer les paramètres de forme "action" et "codeId" avant que quelqu'un décide ne cliquez sur le bouton Soumettre:

<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 Enfin la classe paralele servlet:

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

et ajouter à web.xml, où com.apppack est le paquet de la classe de mise à jour

<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
répondu Campino 2015-08-07 13:29:37