web-dev-qa-db-ja.com

グーグルクラウドエンドポイントとandroid

Eclipseプラグインを使用してAndroidプロジェクトに接続されたアプリエンジンを開発しています。アプリの1つの側面は、ユーザーAlphaがユーザーBravoに写真を送信できるようにすることです。これを行うには、次の設定が必要です。

ユーザーアルファ投稿:

  • エンドポイントを介してアプリエンジンサーバーに画像を送信する
  • サーバーは画像をBLOBストアに保存します
  • サーバーはblobkeyをデータストアに保存します

ユーザーBravoの取得:

  • サーバーはデータストアからblobkeyを取得します
  • サーバーはblobキーを使用して画像を取得します
  • サーバーはエンドポイントを使用して画像をAndroidアプリに送信します

この設定には、私のAndroidアプリが画像を送信してから、ブロブの痛みで画像が表示されるまでに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);
    }

公式の例 エンドポイントを使用する(app-engineインスタンスを使用する必要がある場合)か、getServingUrl(インスタンスをバイパスする)を使用して保存する方法を知っている人はいますか?そして私のブロブを提供しますか?
単語の代わりにコードを含めてください。ありがとう。

32
Pouton Gerald

私がこれをどのように行っているかを共有します。私は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(サーバー)

サーバー側では、アップロード要求サーブレットは次のようになります。

_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の提供、および/またはそれをクライアントに返す処理を行います。ステップ4を処理するサーブレットをそのURLにマップする必要があります

ステップ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でこのリクエストがサーブレットに送信されると、前にcreateUploadUrl()で指定したサーブレットにリダイレクトされます。

ステップ4(サーバー)

サーバー側に戻る:これは、_blob/upload_にマップされたURLを処理するサーブレットです。ここでは、ブロブキーとサービングURLを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();
_

ステップ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について

サービングURLは、オンザフライで画像を動的に拡大縮小できるため、クライアントに画像を提供するための優れた方法です。たとえば、配信URLの最後に_=sXXX_を追加するだけで、LDPIユーザーに小さな画像を送信できます。ここで、XXXは画像の最大サイズのピクセルサイズです。インスタンスを完全に回避し、帯域幅に対してのみ料金を支払い、ユーザーは必要なものだけをダウンロードします。

PS!

ステップ3でuserIdf.exを渡すことにより、ステップ4で停止し、そこに直接保存することができるはずです。パラメーターはすべてステップ4に送信されるはずですが、それが機能しませんでした。現時点でのやり方ですので、うまくいくことがわかっているので、この方法で共有しています。

32
Joachim

この質問の回答を使用して、AppEngineエンドポイントを使用する独自のシステムを構築しました。上記の投稿とは異なり、画像を(バイト配列として)Googleエンドポイントに直接送信するクリーンなAPIが必要であり、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からのコールバックを処理するサーブレット

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 to BlobstorageServiceは常に404NOT FOUNDを返します)

5
Flo

エンドポイントのAPIでコールバックサービスを実行するためにさまざまな方法を試したので、そのアプローチを中止します。ただし、APIエンドポイントへの並列サーブレットを作成することでその問題を解決できました。クラスサーバーを定義してweb.xml構成を追加するだけで済みます。ここに私の解決策:

1アップロード用のURLを取得するためのEnpointサービス:次に、サービスは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(); 

。次のステップは、最後のステップでキャッチされたURLへの投稿を行うことです: AndroidのhttpClientでそれを行うことができます。ここでも、私の場合、Webからアップロードする必要があり、フォームを使用します。そして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サーブレットクラス:

@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クラスのパッケージです。

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