32

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

用户 Alpha 发布:

  • 通过端点将图像发送到我的应用引擎服务器
  • 服务器将图像存储在 blob 存储中
  • 服务器将 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?
请包含代码,而不是文字。谢谢。

4

3 回答 3

32

我将分享我是如何做到这一点的。我没有使用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 步(服务器)

回到服务器端:这是处理映射到 url 的 servlet blob/upload。我们将在此处以 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 是向客户提供图像的好方法,因为它可以动态地动态缩放图像。例如,您可以将较小的图像发送给您的 LDPI 用户,只需=sXXX在服务 url 的末尾附加即可。其中 XXX 是图像最大尺寸的像素大小。你完全避免了你的实例,只为带宽付费,用户只下载她需要的东西。

PS!

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

于 2013-06-02T21:08:05.527 回答
5

我使用这个问题的答案来构建我自己的使用 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://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 在本地运行时,这似乎不起作用(如果在本地执行,那么到 BlobstorageService 的 POST 将始终返回 404 NOT FOUND)

于 2015-07-21T13:15:34.457 回答
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:

1 Enpoint Service for get the URL for upload: Then the service coudl be protected with 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):

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:

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

@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

<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>
于 2015-02-27T19:50:59.307 回答