将 blobstore 与谷歌云端点和安卓一起使用

Posted

技术标签:

【中文标题】将 blobstore 与谷歌云端点和安卓一起使用【英文标题】:using blobstore with google cloud endpoint and android 【发布时间】:2013-04-21 05:39:11 【问题描述】:

我正在使用 eclipse 插件开发一个应用引擎连接的 android 项目。该应用程序的一个方面是允许用户 Alpha 向用户 Bravo 发送图片。为此,我有以下设置:

用户 Alpha 发布:

通过端点将图像发送到我的应用引擎服务器 服务器将图像存储在 Blob 存储中 服务器将 blobkey 存储在数据存储中

用户 Bravo 获得:

服务器从数据存储中获取 blobkey 服务器使用 blob 键获取图像 服务器使用端点向安卓应用发送图像

从我的 android 应用程序发送图像到我可以在 blob 疮中看到它,此设置需要两 (2) 分钟以上。不用说这是完全不能接受的。

我的服务器正在通过以下代码以编程方式处理图像:

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

有谁知道我如何调整the official example 以使用端点(在我必须使用我的应用程序引擎实例的情况下)或使用getServingUrl(绕过我的实例)来存储和服务我的 blob? 请不要包含文字,而是包含代码。谢谢。

【问题讨论】:

我自己一直在摆弄 blobstore 一段时间。没有让它按我的喜好工作,最后最终使用驱动 api 在用户之间交换图片。非常容易实现,并且像魅力一样工作。也许这也是你的一个选择...... 接受的回复确实是一个很好的答案,但如果有人能给出端点答案会更好。特别是,我如何让回调 url 指向一个端点方法?如果有人为我回答这个问题,那就太好了。 【参考方案1】:

由于我尝试了多种方法在端点的 api 中进行回调服务,所以我放弃了这种方法。但是,我可以解决为 api 端点创建并行 servlet 的问题,它只需要定义类服务器并添加 web.xml 配置。这是我的解决方案:

1 Enpoint Service 用于获取上传 URL: 然后可以使用 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。现在客户端可以调用端点来获取上传 URL: 也许有些像这样(对于 android 也使用你的客户端库 enpoint):

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

3.下一步是在上一步捕获的 url 上发布一个帖子: 您可以使用 android 的 httpClient 再次执行此操作,在我的情况下,我需要从网络上传,然后我使用表单,并使用 onChangeFile() 事件回调来获取 uploadurl(使用步骤 3),然后当它响应更改表单时在有人决定点击提交按钮之前参数“action”和“codeId”:

<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 最后是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();
    

并添加到web.xml中,其中com.apppack是Update Class的包

<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】:

我使用这个问题的答案来构建我自己的使用 AppEngine Endpoints 的系统。与上面的帖子不同,我想要一个干净的 API 直接将图像(作为字节数组)传输到 Google Endpoint,并且上传到 BlobstorageService 是在后端完成的。这样做的好处是我有一个原子 API。缺点显然是服务器上的负载以及客户端上繁重的编组操作。

Android - 加载、缩放和序列化图像并上传到端点

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

后端 API 端点 - 将图像上传到 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 ...

处理来自 BlobstorageService 的回调的 Servlet

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

剩下要做的就是将/blob/upload绑定到UploadBlobServlet。

注意:当 AppEngine 在本地运行时,这似乎不起作用(如果在本地执行,那么到 BlobstorageService 的 POST 将始终返回 404 NOT FOUND)

【讨论】:

如何将 '/blob/upload' 绑定到 UploadBlobServlet。? 您可以通过端点方法上传的图像大小没有 1mb 的限制吗?感谢您分享代码顺便说一句 @MicroR 不,限制是 10MiB。 cloud.google.com/datastore/docs/concepts/limits【参考方案3】:

我将分享我是如何做到这一点的。我没有使用 google-cloud-endpoints,而只是我自己的基于 rest 的 api,但无论哪种方式都应该是相同的想法。

我将用代码一步一步地列出它,希望它会很清楚。 您只需调整发送请求的方式以使用端点,而不是像本例中那样更通用。为简洁起见,我包含了一些样板文件,但不包括 try/catch、错误检查等。

第 1 步(客户端)

第一个客户端向服务器请求上传 url:

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

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

第 2 步(服务器)

在服务器端,上传请求 servlet 看起来像这样:

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

注意 createUploadUrl 的参数。这是客户所在的地方 实际上传完成后重定向。那是哪里 您将处理存储 blobkey 和/或提供 url 并将其返回给客户端。您必须将 servlet 映射到该 url,它将处理第 4 步

第 3 步(客户端) 再次返回客户端,使用第 2 步返回的 url 将实际文件发送到上传 url。

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)

一旦在步骤 2 中将此请求发送到 servlet,它将被重定向到您在前面的 createUploadUrl() 中指定的 servlet

第 4 步(服务器)

回到服务器端: 这是处理映射到blob/upload 的url 的servlet。我们将在此处以 json 对象的形式将 blobkey 和服务 url 返回给客户端:

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

第 5 步(客户端)

我们将从 json 中获取 blobkey 和服务 url,然后将其与用户 ID 等一起发送到数据存储实体中。

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

第 6 步(服务器) 实际上将所有内容存储在数据存储中(在本例中使用 objectify)

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

我希望这能让事情更清楚。如果有人想编辑答案以使用云端点而不是这个更通用的示例,请随意:)

关于服务网址

服务网址是向客户提供图片的好方法,因为它可以动态地动态缩放图片。例如,您可以通过在服务 url 的末尾附加 =sXXX 来向您的 LDPI 用户发送较小的图像。其中 XXX 是图像最大尺寸的像素大小。您完全避免使用您的实例,只需为带宽付费,而用户只下载她需要的内容。

PS!

应该可以在步骤 4 停止并直接将其存储在那里,方法是在步骤 3 中传递 userId f.ex。任何参数都应该发送到步骤 4,但我没有让它工作,所以这就是我目前的做法,所以我以这种方式分享它,因为我知道它有效。

【讨论】:

很好的答案!这对我有很大帮助!我只有一个问题。 createUploadUrl() 方法中的 url 是相对的还是绝对的?我的意思是,我的 serverlet 有这个 url:192.168.0.6:8888/1/api/blobs/upload,我想在 createUploadUrl() 参数中写什么?所有的路径? @FlavienBert 不确定绝对 URL 是否有效,但相对 URL 肯定有效。试试看:) 我已经设法分四步完成了,如果有人有兴趣,我会发布解决方案。不过,如果没有本教程,我什么也做不了……谢谢 Joachim! 你可以使用端点而不是第一个 servlet @ApiMethod(name = "getBlobURL") public BlobAttributes getBlobURL() throws Exception BlobstoreService blobstoreService = BlobstoreServiceFactory.getBlobstoreService();字符串 blobUploadUrl = blobstoreService.createUploadUrl("/getURL"); BlobAttributes ba= new BlobAttributes(); ba.setBlobURl(blobUploadUrl);返回吧; @AhmadSaadFares resultJsonString 是第 4 步中发送回客户端的响应

以上是关于将 blobstore 与谷歌云端点和安卓一起使用的主要内容,如果未能解决你的问题,请参考以下文章

将 github 存储库与谷歌云存储桶同步

Ticketek将MongoDB Atlas与谷歌云相结合, 助力分析应用

腾讯云与谷歌云达成合作?产品思维转变亦是王道!

谷歌云数据存储与谷歌应用引擎

谷歌云 pubsub node.js 客户端与谷歌云功能不兼容

text 与谷歌云工具合作intellij。