korzystanie z blobstore z Google cloud endpoint i Androidem

Rozwijam Projekt App-engine połączony z Androidem za pomocą wtyczki eclipse. Jednym z aspektów aplikacji jest umożliwienie użytkownikowi Alpha wysyłania zdjęć do użytkownika Bravo. W tym celu mam następującą konfigurację:

User Alpha posting:

  • wyślij obraz do mojego serwera app engine przez punkty końcowe
  • serwer przechowuje obraz w sklepie blob
  • serwer przechowuje klucz blobkey w datastore

User Bravo getting:

  • serwer pobiera blobkey z datastore
  • serwer pobiera obraz za pomocą klucza blob
  • serwer wysyła obraz do aplikacji android za pomocą punktów końcowych

Ta konfiguracja trwa dwie (2) minuty, od kiedy moja aplikacja na Androida wysyła obraz do kiedy mogę go zobaczyć w blob ból. Nie trzeba dodawać, że jest to całkowicie niedopuszczalne.

Mój serwer przetwarza obraz programowo, za pomocą następującego kodu:

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

Czy ktoś wie jak Mogę dostosować oficjalny przykład do użycia punktów końcowych (w przypadku, gdy muszę używać instancji app-engine) lub używać getServingUrl (omijając moje instancje) do przechowywania i serwowania moich obiektów blob?
proszę zamiast słów podać kod. Dzięki.

Author: Lazy Ninja, 2013-04-27

3 answers

Podzielę się tym, jak to robię. Nie używam Google-cloud-endpoints, ale tylko mojego własnego API opartego na rest, ale powinien być ten sam pomysł tak czy inaczej.

Ułożę to krok po kroku z kodem, mam nadzieję, że będzie jasne. Po prostu dostosujesz sposób wysyłania żądań, aby używać punktów końcowych, zamiast robić to bardziej ogólnie, jak w tym przykładzie. Jestem w tym trochę boilerplate, ale wyłączając try / catch, sprawdzanie błędów itp dla zwięzłości.

Krok 1 (klient)

Pierwszy klient żąda adresu URL z serwera:

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

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

Krok 2 (Serwer)

Po stronie serwera Servlet żądania uploadu wyglądałby mniej więcej tak:

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

zwróć uwagę na argument createUploadUrl. Tutaj klient będzie przekierowanie po zakończeniu rzeczywistego przesyłania. Tam właśnie będziesz zajmować się przechowywaniem klucza blobkey i / lub serwowaniem url i zwracaniem go Klientowi. Będziesz musiał mapować servlet do ten adres url, który będzie obsługiwał Krok 4

Krok 3 (klient) Wróć ponownie do klienta, aby wysłać rzeczywisty plik do adresu URL przesłanego za pomocą adresu URL zwróconego z kroku 2.

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)

Po wysłaniu tego żądania do servletu w Kroku 2, zostanie ono przekierowane do servletu określonego w createUploadUrl() wcześniej

Krok 4 (Serwer)

Powrót do strony serwera: Jest to servlet obsługujący adres URL odwzorowany na blob/upload. Tu zwrócimy blobkey ' a i adres url do Klienta w obiekcie 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();

Krok 5 (Klient)

Otrzymamy klucz blobkey i adres URL z json, a następnie wyślemy go wraz z ID użytkownika itp. do przechowywania w encji datastore.

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

Krok 6 (Serwer) Faktycznie przechowuje wszystko w magazynie danych (używając objectify w tym przykładzie)

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);
Mam nadzieję, że to wszystko wyjaśni. Jeśli ktoś chce edytować odpowiedź, aby użyć cloud endpoints zamiast tego bardziej ogólnego przykład: feel free:)

O serwowaniu url

Adres URL serwowania to świetny sposób na serwowanie obrazów klientom, ze względu na sposób, w jaki może dynamicznie skalować obrazy w locie. Na przykład możesz wysyłać mniejsze obrazy do użytkowników LDPI, po prostu dodając =sXXX na końcu adresu URL. Gdzie XXX to rozmiar piksela największego wymiaru obrazu. Całkowicie unikasz swoich instancji i płacisz tylko za przepustowość, a użytkownik pobiera tylko to, co ona potrzeb.

PS!

Powinno być możliwe, aby zatrzymać się w kroku 4 i po prostu przechowywać go bezpośrednio tam, przechodząc wzdłuż userId f. ex w kroku 3. Wszelkie parametry powinny być wysłane do kroku 4, ale nie udało mi się to zrobić, więc tak to robię w tej chwili, więc dzielę się tym w ten sposób, ponieważ Wiem, że działa.

 32
Author: Joachim,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-06-02 21:08:05

Użyłem odpowiedzi na to pytanie, aby zbudować własny system, który wykorzystuje punkty końcowe AppEngine. W przeciwieństwie do powyższych postów, chcę mieć czyste API, które bezpośrednio przesyła obraz (jako tablicę bajtów) do punktu końcowego Google, a przesyłanie do BlobstorageService odbywa się po stronie zaplecza. Zaletą tego jest to, że mam atomic API. Wadą oczywiście obciążenie serwera, a także ciężkie operacje rozlokowania na kliencie.

Android-załaduj, Skaluj i serializuj obraz and upload to endpoints

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

Backend API Endpoint-Upload image to 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 obsługujący callback z 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();
    }
}

Pozostało tylko związać /blob/upload z UploadBlobServlet.

Uwaga: to nie działa, gdy AppEngine jest uruchomiony lokalnie (jeśli zostanie uruchomiony lokalnie, to POST do BlobstorageService zawsze zwróci 404 NOT FOUND)

 5
Author: Flo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-07-21 13:15:34

Ponieważ próbowałem z wielu sposobów, aby zrobić usługę zwrotną w API punktu końcowego, przerywam to aproach. Jednak mógłbym rozwiązać ten problem tworząc równoległy servlet do punktu końcowego api, wystarczy zdefiniować serwer klasy i dodać go do sieci.konfiguracja xml. Oto moje rozwiązanie:

1 usługa Enpoint dla uzyskania adresu URL do uploadu: Następnie usługa może być chroniona za pomocą 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. Teraz klient może zadzwonić do punktu końcowego, aby uzyskać przesyłanie URL:
Może coś takiego (dla Androida używasz też biblioteki klienckiej enpoint):

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

3. Następnym krokiem jest wysłanie Posta do adresu URL przechwyconego w ostatnim kroku: Możesz to zrobić z httpClient Androida, ponownie, w moim przypadku muszę przesłać z Internetu, a następnie użyć formularza, a onchangefile () callback event dla get uploadurl (za pomocą kroku 3) następnie, gdy odpowiedź na zmianę parametrów formularza "Akcja " i" codeId " zanim ktoś zdecyduje, kliknij przycisk Wyślij:

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

I dodać do sieci.xml, gdzie com.apppack jest pakietem klasy 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
Author: Campino,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-08-07 13:29:37