使用blobstore与谷歌云端点和android



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

User Alpha发帖:

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

用户Bravo获取:

  • 服务器从数据存储
  • 获取blobkey
  • 服务器使用blob键获取图像
  • 服务器发送图像到android应用程序使用端点

这个设置从我的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);
    }

有没有人知道我如何能够适应官方的例子来使用端点(在我必须使用我的应用程序引擎实例的情况下)或使用getServingUrl(绕过我的实例)来存储和服务我的blob ?
请用代码代替文字。谢谢。

我将分享我是如何做到这一点的。我不使用谷歌云端点,但只是我自己的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,该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

step4 (server)

回到服务器端:这是处理映射到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();

step5 (client)

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

step6 (server)实际将所有内容存储在数据存储中(在本例中使用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

服务url是向客户端提供图像的好方法,因为它可以动态地缩放图像。例如,只需在服务url的末尾附加=sXXX,就可以向LDPI用户发送较小的图像。其中XXX为图像最大维度的像素大小。你完全避免了实例,只支付带宽,用户只下载他需要的东西。

PS !

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

我使用这个问题的答案来构建我自己的使用AppEngine端点的系统。与上面的帖子不同,我想有一个干净的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://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();
}

后端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在本地运行时,这似乎不起作用(如果在本地执行,那么POST到BlobstorageService将始终返回404 NOT FOUND)

由于我尝试了许多方法在端点的api中执行回调服务,因此我放弃了该方法。然而,我可以解决这个问题,使并行servlet到api端点,它只需要定义类服务器和添加它的web.xml配置。下面是我的解决方案:

1获取上传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也使用你的客户端库端点):

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

3。下一步是发送到上一步捕获的url:你可以这样做与android的httpClient,再一次,在我的情况下,我需要从web上传然后我使用一个表单,onChangeFile()事件回调获取uploadurl(使用步骤3)然后当它响应改变表单参数"action"one_answers"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>
最后是并行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>

最新更新