使用blobstore与谷歌云端点和android

时间:2021-06-11 20:19:34

I am developing an app-engine connected android project using the eclipse plugin. One aspect of the app is to allow user Alpha to send pictures to user Bravo. To do that I have the following setup:

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

User Alpha posting:

用户Alpha发布:

  • send image to my app engine server through endpoints
  • 通过端点将图像发送到我的应用引擎服务器
  • server stores image in blob store
  • 服务器将图像存储在blob存储中
  • server stores blobkey in datastore
  • 服务器在数据存储区中存储blobkey

User Bravo getting:

用户Bravo获得:

  • server gets blobkey from datastore
  • 服务器从数据存储区获取blobkey
  • server gets image using blob key
  • 服务器使用blob密钥获取图像
  • server sends image to android app using endpoints
  • 服务器使用端点将图像发送到Android应用程序

This setup takes upward of two (2) minutes from when my android app sends an image to when I can see it in the blob sore. Needless to say this is completely unacceptable.

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

My server is processing the image programmatically, thru the following code:

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

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

Does anyone know how I can either adapt the official example to use endpoints (in the case where I must use my app-engine instances) or use getServingUrl (bypassing my instances) to store and serve my blobs?
Please, instead of words, include the code. Thanks.

有谁知道如何调整官方示例以使用端点(在我必须使用我的app-engine实例的情况下)或使用getServingUrl(绕过我的实例)来存储和提供我的blob?请使用代码来代替单词。谢谢。

3 个解决方案

#1


32  

I'll share how I'm doing this. I'm not using the google-cloud-endpoints, but just my own rest based api, but it should be the same idea either way.

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

I'll lay it out step by step with code, hopefully it will be clear. You'd simply adapt the way you send your requests to use endpoints instead of doing it more generic like in this example. I'm including some boilerplate, but excluding try/catch,error checking etc for brevity.

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

Step 1 (client)

第1步(客户)

First client requests an upload url from server:

第一个客户端从服务器请求上传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);

Step 2 (server)

第2步(服务器)

On the server side the upload request servlet would look something like this:

在服务器端,上传请求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();

note the argument to createUploadUrl. This is where the client will be redirected once the actual upload has been completed. That's where you'll handle storing the blobkey and/or serving url and returning it to the client. You'll have to map a servlet to that url, which will handle step 4

请注意createUploadUrl的参数。这是实际上传完成后客户端将被重定向的位置。这就是您将处理存储blobkey和/或服务URL并将其返回给客户端的位置。您必须将servlet映射到该URL,该URL将处理第4步

Step 3 (client) Back to the client again to send the actual file to the upload url using the url returned from step 2.

步骤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)

Once this request is sent to the servlet in step 2, it will be redirected to the servlet you specified in the createUploadUrl() earlier

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

Step 4 (server)

第4步(服务器)

Back to the server side: This is the servlet handling the url mapped to blob/upload. We will here return the blobkey and serving url to the client in a json object:

回到服务器端:这是处理映射到blob / upload的url的servlet。我们将在这里返回blobkey并在json对象中将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();

Step 5 (client)

第5步(客户端)

We'll get the blobkey and serving url from the json and then send it along with user id etc to store in the datastore entity.

我们将从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

Step 6 (server) Actually storing everything in the datastore (using objectify in this example)

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

I hope this makes things more clear. If someone wants to edit the answer to use cloud endpoints instead of this more generic example, feel free :)

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

About the serving url

关于服务网址

The serving url is a great way to serve images to your clients, because of the way it can dynamically scale images on the fly. For example you can send smaller images to your LDPI users by simply appending =sXXX at the end of the serving url. Where XXX is the pixel size of the largest dimension of your image. You completely avoid your instances and only pay for bandwidth, and the user only downloads what she needs.

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

PS!

PS!

It should be possible to stop at step 4 and just store it directly there, by passing along userId f.ex in step 3. Any parameters are supposed to be sent along to Step 4, but I did not get that to work, so this is how I do it at the moment, so I'm sharing it this way since i know it works.

应该可以在步骤4停止并直接将其存储在那里,通过在步骤3中传递userId f.ex。任何参数都应该发送到步骤4,但我没有得到它,所以这我是如何做到这一点的,所以我这样分享,因为我知道它的工作原理。

#2


5  

I used the answer of this question to build my own system that uses AppEngine Endpoints. Unlike the posts above, I want to have a clean API that directly transmits the image (as byte array) to Google Endpoint and the upload to BlobstorageService is done on the backend side. The benefit of that is that i have an atomic API. The drawback obviously the load on the server as well as the heavy marshalling operations on the client.

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

Android - load, scale and serialize image and upload to endpoints

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

Backend API Endpoint - Upload image to BlobstorageService

后端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 ...
}

Servlet that handles callback from BlobstorageService

处理来自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();
    }
}

The only thing left to do is to bind /blob/upload to UploadBlobServlet.

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

Note: This doesn't seem to work when AppEngine is running locally (if executed locally, then the POST to BlobstorageService would always return a 404 NOT FOUND)

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

#3


2  

Since I tried with many way to do the callback service in the api of endpoint, I abort that aproach. However, I could solve that problem making a parallel servlet to the api endpoint, it only needs define the class server and add it web.xml configuration. Here my solution:

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

1 Enpoint Service for get the URL for upload: Then the service coudl be protected with clientId

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. Now the Client can call to endpoint for get the upload URL:
Maybe some like this (for android use you client library enpoint too):

2.现在客户端可以调用端点来获取上传URL:也许有些像这样(对于android也使用你的客户端库enpoint):

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

3. The next step is todo a post to url catched in last step: You can do that with a httpClient of android, again, in my case I need upload from a web then I use a form, and onChangeFile() event callback for get the uploadurl (using step 3) then when it response to change the form parameters "action" and "codeId" before that someone decide do click on submit button:

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 Finally the paralele servlet class:

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

and add to web.xml, where com.apppack is the package of Update Class

并添加到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>

#1


32  

I'll share how I'm doing this. I'm not using the google-cloud-endpoints, but just my own rest based api, but it should be the same idea either way.

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

I'll lay it out step by step with code, hopefully it will be clear. You'd simply adapt the way you send your requests to use endpoints instead of doing it more generic like in this example. I'm including some boilerplate, but excluding try/catch,error checking etc for brevity.

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

Step 1 (client)

第1步(客户)

First client requests an upload url from server:

第一个客户端从服务器请求上传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);

Step 2 (server)

第2步(服务器)

On the server side the upload request servlet would look something like this:

在服务器端,上传请求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();

note the argument to createUploadUrl. This is where the client will be redirected once the actual upload has been completed. That's where you'll handle storing the blobkey and/or serving url and returning it to the client. You'll have to map a servlet to that url, which will handle step 4

请注意createUploadUrl的参数。这是实际上传完成后客户端将被重定向的位置。这就是您将处理存储blobkey和/或服务URL并将其返回给客户端的位置。您必须将servlet映射到该URL,该URL将处理第4步

Step 3 (client) Back to the client again to send the actual file to the upload url using the url returned from step 2.

步骤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)

Once this request is sent to the servlet in step 2, it will be redirected to the servlet you specified in the createUploadUrl() earlier

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

Step 4 (server)

第4步(服务器)

Back to the server side: This is the servlet handling the url mapped to blob/upload. We will here return the blobkey and serving url to the client in a json object:

回到服务器端:这是处理映射到blob / upload的url的servlet。我们将在这里返回blobkey并在json对象中将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();

Step 5 (client)

第5步(客户端)

We'll get the blobkey and serving url from the json and then send it along with user id etc to store in the datastore entity.

我们将从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

Step 6 (server) Actually storing everything in the datastore (using objectify in this example)

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

I hope this makes things more clear. If someone wants to edit the answer to use cloud endpoints instead of this more generic example, feel free :)

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

About the serving url

关于服务网址

The serving url is a great way to serve images to your clients, because of the way it can dynamically scale images on the fly. For example you can send smaller images to your LDPI users by simply appending =sXXX at the end of the serving url. Where XXX is the pixel size of the largest dimension of your image. You completely avoid your instances and only pay for bandwidth, and the user only downloads what she needs.

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

PS!

PS!

It should be possible to stop at step 4 and just store it directly there, by passing along userId f.ex in step 3. Any parameters are supposed to be sent along to Step 4, but I did not get that to work, so this is how I do it at the moment, so I'm sharing it this way since i know it works.

应该可以在步骤4停止并直接将其存储在那里,通过在步骤3中传递userId f.ex。任何参数都应该发送到步骤4,但我没有得到它,所以这我是如何做到这一点的,所以我这样分享,因为我知道它的工作原理。

#2


5  

I used the answer of this question to build my own system that uses AppEngine Endpoints. Unlike the posts above, I want to have a clean API that directly transmits the image (as byte array) to Google Endpoint and the upload to BlobstorageService is done on the backend side. The benefit of that is that i have an atomic API. The drawback obviously the load on the server as well as the heavy marshalling operations on the client.

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

Android - load, scale and serialize image and upload to endpoints

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

Backend API Endpoint - Upload image to BlobstorageService

后端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 ...
}

Servlet that handles callback from BlobstorageService

处理来自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();
    }
}

The only thing left to do is to bind /blob/upload to UploadBlobServlet.

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

Note: This doesn't seem to work when AppEngine is running locally (if executed locally, then the POST to BlobstorageService would always return a 404 NOT FOUND)

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

#3


2  

Since I tried with many way to do the callback service in the api of endpoint, I abort that aproach. However, I could solve that problem making a parallel servlet to the api endpoint, it only needs define the class server and add it web.xml configuration. Here my solution:

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

1 Enpoint Service for get the URL for upload: Then the service coudl be protected with clientId

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. Now the Client can call to endpoint for get the upload URL:
Maybe some like this (for android use you client library enpoint too):

2.现在客户端可以调用端点来获取上传URL:也许有些像这样(对于android也使用你的客户端库enpoint):

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

3. The next step is todo a post to url catched in last step: You can do that with a httpClient of android, again, in my case I need upload from a web then I use a form, and onChangeFile() event callback for get the uploadurl (using step 3) then when it response to change the form parameters "action" and "codeId" before that someone decide do click on submit button:

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 Finally the paralele servlet class:

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

and add to web.xml, where com.apppack is the package of Update Class

并添加到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>